241109 noip 模拟赛

省流:\(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)\)

代码:

#include<bits/stdc++.h> #define int long long using namespace std; const int N=5e5+5; int T,n,id,q,a[N],st[N]; inline int pos(int x,int y) { if(!x) return 0; return (a[x]-a[y])/(y-x)+((a[x]-a[y])%(y-x)?1:0); } inline void write(int x) { int wr[20],tot=0; while(x) wr[++tot]=x%10,x/=10; for(int i=tot; i>=1; i--) putchar(wr[i]+'0'); if(!tot) putchar('0'); } signed main() { ios::sync_with_stdio(false); cin.tie(nullptr),cout.tie(nullptr); cin>>T>>n>>id; while(T--) { cin>>q; int lim=LONG_LONG_MAX; for(int i=1; i<=n; i++) { cin>>a[i]; lim=min(lim,(LONG_LONG_MAX-a[i])/i); } int top=0;st[++top]=0; for(int i=1; i<=n; i++) { while(top>1&&pos(st[top],i)<=pos(st[top-1],st[top])) top--; st[++top]=i; } vector<pair<pair<int,int>,int>> ve; for(int i=2; i<top; i++) ve.push_back(make_pair(make_pair(pos(st[i-1],st[i]),pos(st[i+1],st[i])-1),st[i])); ve.push_back(make_pair(make_pair(pos(st[top-1],st[top]),lim),st[top])); int ans=0,sum=0,pos=0,k; while(q--) { cin>>k; if(id) k=(k^(ans%(k+1))); sum+=k; while(pos<ve.size()&&ve[pos].first.second<sum) pos++; write(ans=a[ve[pos].second]+ve[pos].second*sum);putchar('\n'); } } return 0; }

闲话: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)\)

代码:

#include<bits/stdc++.h> using namespace std; const int N=1e6+5; int n,k,x[N],dis[N],fa[N],d[N],vis[N],genshin[N],ans=0,head[N],ecnt=0; struct edge {int to,nxt;}e[N<<1]; void add(int u,int v) {e[++ecnt]=(edge){v,head[u]};head[u]=ecnt;} bool cmp(int a,int b) {return d[a]>d[b];} void dfs1(int u,int pre,int dep) { fa[u]=pre,d[u]=dep; for(int i=head[u]; i; i=e[i].nxt) { int v=e[i].to; if(v==pre) continue; dfs1(v,u,dep+1); } } void dfs2(int u,int pre,int dist) { vis[u]=true; if(!dis) return; for(int i=head[u]; i; i=e[i].nxt) { int v=e[i].to; if(v==pre||dis[v]!=dist-1||vis[v]) continue; dfs2(v,u,dist-1); } } int main() { cin>>n>>k; for(int i=1; i<n; i++) { int u,v; cin>>u>>v; add(u,v),add(v,u); } memset(dis,0x3f,sizeof(dis)); queue<int> q; for(int i=1; i<=k; i++) { cin>>x[i]; dis[x[i]]=0; q.push(x[i]); } while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u]; i; i=e[i].nxt) { int v=e[i].to; if(dis[v]>dis[u]+1) { dis[v]=dis[u]+1; q.push(v); } } } dfs1(1,0,1); sort(x+1,x+k+1,cmp); for(int i=1; i<=k; i++) { if(!vis[x[i]]) { int tmp=x[i]; while(fa[tmp]&&dis[fa[tmp]]==dis[tmp]+1) tmp=fa[tmp]; genshin[++ans]=tmp; dfs2(tmp,0,d[x[i]]-d[tmp]); } } cout<<ans<<endl; for(int i=1; i<=ans; i++) cout<<genshin[i]<<" "; return 0; }

闲话: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__

本文作者System_Error
本文链接https://www.cnblogs.com/System-Error/p/18536697.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   System_Error  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示