省流:\(100+35+1+0\) 遗憾离场。
T1
题意:给一个长度为 \(n\) 的序列 \(a\),需要进行 \(q\) 次操作,每次给定一个 \(k\) 使 \(a_i = a_i + i \times k\) 操作后求出序列的最大值,强制在线。
\(n,q \leq 5 \times 10^5,1 \leq a_i,k \leq 10^{18}\),保证答案不超过 long long
的范围。
设 \(s_i\) 表示前 \(i\) 个操作的 \(k\) 的和,则此时第 \(i\) 个位置的值为 \(i \times s_i + a_i\),发现这是一个一次函数的形式,可以用单调栈维护下凸壳,由于 \(s_i\) 是递增的,所以用一个指针记录当前取得最大值的是哪个位置,把 \(s_i\) 代进去算一下就好了,时间复杂度 \(\Theta(n)\)。
代码:
闲话:win 7机子跑的是真的慢,考场测大样例跑了4s,虚空卡常1h,但不管怎么卡都是4s,最终出结果最慢点0.4s(
T2
原题 P8428。
题意:给定 \(n\) 个点的树,树上有 \(k\) 个关键点,你可以选取若干个点(可以是关键点),每个点可以覆盖离自己最近的关键点(如有多个则都可以覆盖)。最小化选取的点数,使得每个关键点都可以覆盖到,输出方案。
\(1 \leq k \leq n \leq 10^6\)。
有一个贪心策略就是每次选择一个深度最大的关键点,如果它还没被覆盖,则找到深度最小的能够覆盖它的点,用这个点进行覆盖。证明个人感觉比较类似于今年 csp-s t2,虽然只有我这么认为。
直接模拟就是 \(\Theta(n^2)\) 的,可以获得35的高分,我赛时就这么写的。
考虑优化:预处理出来 \(dis_u\) 表示离这个点最近的关键点到它的距离,那么覆盖递归时,若 \(dis_v=dis_u + 1\) 并且 \(v\) 没有被覆盖过才访问 \(v\),否则不访问。证一下这个的正确性,由于覆盖的点是所有到这个点距离小于等于这个点的 \(dis\) 的点,所以如果 \(dis_v != dis_u + 1\) 说明继续往下递归不可能覆盖到关键点,所以不需要覆盖,而如果这个点已经被覆盖了,则它往下递归能够覆盖的点也已经全部被覆盖了,所以这样是正确的。可以画图感性理解一下。这样每个点至多被覆盖一次,均摊复杂度为 \(\Theta(n)\),预处理 \(dis\) 可以用多源 BFS,时间复杂度也是 \(\Theta(n)\),所以总时间复杂度是 \(\Theta(n)\)。
代码:
闲话:noip模拟t2放紫题素质呢/fn。不过这题也是真唐,不知道赛时在抽什么风,这都做不出。
T3
题意:给定一张 \(n\) 个点的图,判定该图是否是无自环的无向图且每个点的度数相同且无三元环。 同时用给定的 \(n\),构造每个点度数最大的,满足上述要求的图。
\(n \leq 1000\)。
分类讨论。
第一问拿 bitset
搞就可以了,所以直接看第二问。 假设每个点最大的度数为 \(k\)。
- 结论一:不论 \(n\) 是多少,\(k \leq \frac{n}{2}\)。
这个证明是容易的。考虑反证,假设某个点向外连了超过点数一半的边,那么与它联边的点最多只能 和少于一半的点联边,这就不能让度数相同了。
于是 为偶数的情况就解决了。只要把点均分成两份,每个点向另一份的所有点连边就好。这是二分图。
如果是奇数呢?
- 结论二:如果 \(n\) 是奇数,则 \(k \leq \frac{2n}{5}\)。
刚才提到了二分图,不妨按着这个思路想。
除去 \(k\) 特别小的情况,显然这就不是二分图。也就是说有奇环,假设是 \(L\)。
- 结论三:\(L\) 之外的点与 \(L\) 之内的点联边不超过两条。
反证,假设某个点 \(x\) 与 \(L\) 内的点有三条边,假设连的点按环上的顺序分别是 \(a_1,a_2,a_3\)。这样就会形成三个环,分别是:
- \(x \to a_1 \to \cdots \to a_2 \to x\)
- \(x \to a_2 \to \cdots \to a_3 \to x\)
- \(x \to a_3 \to \cdots \to a_1 \to x\)
显然,这三个环的长度之和为 \(\lvert L \rvert\)。显然这是奇数,所以必然有一个奇环两个偶环。这两个偶环长度和不小于 \(8\),所以这个奇环长度不大于 \(\lvert L \rvert - 2\),这就矛盾了。所以引理正确。
发现这个限制很强,从这个角度找证明。则有:
环上的点的度数之和 \(k \lvert L \rvert \leq 2 \lvert L \rvert + 2 (n - \lvert L \rvert)) = 2n\)。
又因为 \(\lvert L \rvert > 3\)(题目限制),所以移项发现 。得证。
于是,我们只要构造出 \(k = \frac{2n}{5}\) 的解即可。题目又有限制:“含有数码 \(3\) 或数码 \(9\) ,你就会拒绝回答第二问”,也就是只要构造出 \(5n,5n + 1,5n + 2\) 即可。
- \(5n\):把 \(n\) 分成 \(5\) 个部分,每对相邻的部分用二分图的连边方法两两连边即可。
- \(5n + 1\):在 \(5n\) 的基础上把第一个部分减少一个点,第三和四个部分增加一个点,然后相邻的部分用二分图的连边方法两两连边,但是这样第三第四个部分会多出 \(1\) 的度数,使第三第四的部分每个点连的边都减少一个就行。
- \(5n + 2\):同 \(5n + 1\) 把第一部分减少两个,第三第四增加两个,第三第四部分每个点连的边减少两个。
当 \(n = 1\) 或 \(n = 7\) 的时候需要特判。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2005;
char a[N][N];
vector<int> ve[10];
int t,n,c[N],ans[N][N];
bitset<N> b[N];
void addedge(int u,int v,int k) {
if(!k) {
for(int i=0; i<ve[u].size(); i++)
for(int j=0; j<ve[v].size(); j++)
ans[ve[u][i]][ve[v][j]]=ans[ve[v][j]][ve[u][i]]=1;
} else if(k==1) {
for(int i=0; i<ve[u].size(); i++) {
for(int j=0; j<ve[v].size(); j++) {
if(i==j) continue;
ans[ve[u][i]][ve[v][j]]=ans[ve[v][j]][ve[u][i]]=1;
}
}
} else {
int len=ve[u].size();
for(int i=0; i<len; i++) {
for(int j=0; j<len; j++) {
if(i==j||i==(j+1)%len) continue;
ans[ve[u][i]][ve[v][j]]=ans[ve[v][j]][ve[u][i]]=1;
}
}
}
}
bool check(int n) {while(n) {if(n%10==3||n%10==9) return true;n/=10;}return false;}
int main() {
cin>>t;
while(t--) {
cin>>n;
memset(c,0,sizeof(c));
for(int i=0;i<n;i++)for(int j=0;j<n;j++)cin>>a[i][j],a[i][j]-='0',b[i][j]=a[i][j],c[j]+=a[i][j];
bool valid=1;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(a[i][j]&&(b[i]&b[j]).count())valid=0;
for(int i=1;i<n;i++)if(b[i].count()!=b[1].count())valid=0;
for(int i=0;i<n;i++)for(int j=i;j<n;j++)if(a[i][j]!=a[j][i])valid=0;
if(valid)cout<<"Yes\n";
else cout<<"No\n";
for(int i=0;i<n;i++)b[i].reset();
if(check(n)) {cout<<"Nope"<<endl;continue;}
if(n%2==0) {
cout<<n/2<<endl;
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
if((i&1)!=(j&1)) cout<<1;
else cout<<0;
}
cout<<endl;
}
} else if(n==1) {
cout<<0<<endl<<0<<endl;
} else if(n==7) {
cout<<"2"<<endl;
cout<<"0100001"<<endl;
cout<<"1010000"<<endl;
cout<<"0101000"<<endl;
cout<<"0010100"<<endl;
cout<<"0001010"<<endl;
cout<<"0000101"<<endl;
cout<<"1000010"<<endl;
} else {
int tmp=n/5;
for(int i=1; i<=5; i++) {
ve[i].clear();
for(int j=(i-1)*(n/5)+1; j<=i*(n/5); j++) ve[i].push_back(j);
}
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) ans[i][j]=0;
cout<<2*tmp<<endl;
if(n%5==0) {
for(int i=1; i<5; i++) addedge(i,i+1,0);
addedge(5,1,0);
} else if(n%5==1) {
int a=ve[1].back();ve[1].pop_back();
ve[3].push_back(a),ve[4].push_back(n);
addedge(1,2,0),addedge(2,3,0),addedge(3,4,1),addedge(4,5,0),addedge(5,1,0);
} else {
int a=ve[1].back();ve[1].pop_back();int b=ve[1].back();ve[1].pop_back();
ve[3].push_back(a),ve[3].push_back(b),ve[4].push_back(n-1),ve[4].push_back(n);
addedge(1,2,0),addedge(2,3,0),addedge(3,4,2),addedge(4,5,0),addedge(5,1,0);
}
for(int i=1; i<=n; i++,cout<<endl) for(int j=1; j<=n; j++) cout<<ans[i][j];
}
}
}
闲话:好难写啊/oh/oh/oh
T4
题意:给一个长度为 \(n\) 初始全为 \(0\) 的序列 \(a,s\) 和一个给定值的序列 \(b\),和一个模数 \(m\),有 \(q\) 次操作,操作形式如下:
第一步:
1 l r d
表示给 \(a\) 在区间 \([l,r]\) 上加 \(d\)。
2 l r d
表示给 \(b\) 在区间 \([l,r]\) 全部改为 \(d\)。
第二步:对于每个 \(i\) 使 \(s_{b_i} = (s_{b_i} + a_i) \% m\)
全部操作完毕后你需要输出 \((m - s_i) \% m\) 的值。
\(n \leq 10^6\)。
用 ODT 维护 \(b\) 序列,用类似扫描线的操作,每次加入一个颜色段就减去当前这个区间的历史和,删除一个颜色段就加上这个区间的历史和,由于每次覆盖增加的颜色段至多为 \(2\),所以时间复杂度正确。
那么如何维护历史和呢?
设当前版本为 \(t\),\(hsum_i\) 表示第 \(i\) 个点的历史和,\(sum_i\) 表示第 \(i\) 个点的当前版本的值,那么再维护一个值 \(v = hsum_i - t \times sum_i\)。每次区间加 \(d\) 相当于给区间内的 \(sum_i + d\),给区间内的 \(v - t \times d\),要查询一个区间 \([l,r]\) 的历史和就是 \(hsum_{l,r} = v_{l,r} + t \times sum_{l,r}\),由于这样的拆的柿子非常巧妙,所以不需要考虑版本加一时 \(v\) 的值的变化。这样只需要实现一个区间加区间求和的数据结构就行了,你非常开心的写了个线段树,发现它t飞了,于是你学了一下树状数组的区间加区间求和,然后就过辣!
参考 @fangzichang 大神的文章,讲的是同一个题。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,q,mod,t,b[N],sum[N];
struct node {
int l,r,c;
bool operator<(const node &o) const {return l<o.l;}
};set<node> s;
struct BIT {
int t1[N],t2[N];
int lowbit(int x) {return x&(-x);}
void add(int p,int x) {int tmp=p;while(p<=n) (t1[p]+=x)%=mod,(t2[p]+=tmp*x%mod)%=mod,p+=lowbit(p);}
int query(int p) {int ans1=0,ans2=0,tmp=p;while(p) (ans1+=t1[p])%=mod,(ans2+=t2[p])%=mod,p-=lowbit(p);return (ans1*(tmp+1)%mod-ans2+mod)%mod;}
void add(int l,int r,int x) {add(l,x),add(r+1,mod-x);}
int query(int l,int r) {return (query(r)-query(l-1)+mod)%mod;}
}t1,t2;
set<node>::iterator split(int x) {
set<node>::iterator it=s.lower_bound((node){x,0,0});
if(it!=s.end()&&(*it).l==x) return it;
it--;
if((*it).r<x) return s.end();
int l=(*it).l,r=(*it).r,c=(*it).c;
s.erase(it);
s.insert((node){l,x-1,c});
return s.insert((node){x,r,c}).first;
}
void assign(int l,int r,int c) {
set<node>::iterator itr=split(r+1),itl=split(l);
for(set<node>::iterator it=itl; it!=itr; it++) sum[(*it).c]=(sum[(*it).c]+t2.query((*it).l,(*it).r)+t*t1.query((*it).l,(*it).r)%mod)%mod;
s.erase(itl,itr);
s.insert((node){l,r,c});
sum[c]=(sum[c]-t2.query(l,r)-t*t1.query(l,r)%mod+mod+mod)%mod;
}
signed main() {
cin>>n>>q>>mod;
for(int i=1; i<=n; i++) cin>>b[i],s.insert((node){i,i,b[i]});
for(int &i=t=0; i<q; i++) {
int op;
cin>>op;
if(op==1) {
int l,r,d;
cin>>l>>r>>d;d%=mod;
t1.add(l,r,d),t2.add(l,r,mod-t*d%mod);
} else {
int l,r,d;
cin>>l>>r>>d;
assign(l,r,d);
}
}
for(set<node>::iterator it=s.begin(); it!=s.end(); it++) sum[(*it).c]=(sum[(*it).c]+t2.query((*it).l,(*it).r)+t*t1.query((*it).l,(*it).r)%mod)%mod;
for(int i=1; i<=n; i++) cout<<(mod-sum[i])%mod<<" ";
return 0;
}
闲话:这题赶在 ABC 的前三分钟补完了,爽!
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探