【学习笔记】CF1322

智商不够,要回炉重造了

没猜出来结论,是不是该退役了

既然没做出来,那么还是要写一下题解。记与右部点 i i i相邻的点的集合为 S i S_i Si,如果 S i = S j S_i=S_j Si=Sj那么合并成一组,其权值为 c i + c j c_i+c_j ci+cj。最后答案是不同组的 c i c_i ci gcd \text{gcd} gcd

这条结论的证明显然是不那么显然的,不过作为 oier \text{oier} oier要求的能力应该是把它给猜出来。但是当你自己做这道题的时候又怎会有信仰相信这里有一条结论呢?

证明也不难。当然我自己没有独立去想 。先把每个节点除以 gcd \text{gcd} gcd,然后相当于证对于任意 k > 1 k>1 k>1,存在一个 S S S满足 f ( S ) f(S) f(S)不被 K K K整除。如果 ∑ c i \sum c_i ci不被 K K K整除那么显然得证,否则取出度数最小的点 i i i,考虑将与其相邻的点从 S S S中删去,那么只有 i i i N ( S ) N(S) N(S)中被删去了,如果 c i c_i ci不被 K K K整除那么显然又得证了,否则就不断重复这个过程,直到有一个点不被 K K K整除为止。显然此时至少还剩 1 1 1个点,因此最后剩下来的 S S S不为空。

#include<bits/stdc++.h> #define pb push_back #define fi first #define se second #define ll long long #define db double #define inf 0x3f3f3f3f using namespace std; const int N=5e5+5; int n,m,T; ll c[N],v[N]; vector<int>g[N]; map<ll,ll>dp; ll gcd(ll x,ll y){ return y==0?x:gcd(y,x%y); } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>T;mt19937 t(114514); while(T--){ cin>>n>>m; for(int i=1;i<=n;i++)cin>>c[i],g[i].clear(); for(int i=1;i<=m;i++){ int x,y; cin>>x>>y,g[y].pb(x); } for(int i=1;i<=n;i++)v[i]=t()%(1ll<<60); dp.clear(); for(int i=1;i<=n;i++){ ll x=0; for(auto j:g[i]){ x^=v[j]; } if(g[i].size()){ dp[x]+=c[i]; } } ll res=0; for(auto it:dp){ res=gcd(res,it.se); } cout<<res<<"\n"; } }

非常标准的 d p dp dp题,所以教我如何快速切掉它。

先不考虑时间复杂度,应该如何计算贡献?

显然每个位置的贡献与跨过这个位置的点的数目有关,因此只要记录一下当前剩余点的数目就能算。因此我们将 l i l_i li从小到大进行处理,也就是倒序 d p dp dp

然而,我犯了一个 非常致命的错误 :我把进位的过程想错了。究其本质,是读题的问题,我没有将死掉的那个选手给删除掉。

事实上这道题目非常简单。有一个非常技巧性的做法:设 d p i , j dp_{i,j} dpi,j表示当前级别为 i i i的人还剩 j j j个时的最大收益。考虑 d p i , j dp_{i,j} dpi,j能被转移到所满足的条件,显然就直接做到 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)了,可以直接通过本题。真的吗

不过这玩意可以用神必的方式优化到 O ( n 2 ) O(n^2) O(n2)观察题解代码应该不难理解。

#include<bits/stdc++.h> #define pb push_back #define fi first #define se second #define ll long long #define db double #define inf 0x3f3f3f3f using namespace std; const int N=5000; const int M=2005; int n,m,l[M],s[M],c[N],dp[N+M][M],ans; void chmax(int &x,int y){ x=max(x,y); } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>m;memset(dp,-0x3f,sizeof dp); for(int i=1;i<=n+m;i++)dp[i][0]=0; for(int i=1;i<=n;i++)cin>>l[i]; for(int i=1;i<=n;i++)cin>>s[i]; for(int i=1;i<=n+m;i++)cin>>c[i]; for(int i=n;i>=1;i--){ for(int j=n;j>=1;j--){ chmax(dp[l[i]][j],dp[l[i]][j-1]+c[l[i]]-s[i]); } for(int j=l[i];j<=n+m;j++){ for(int k=0;k<=n>>min(20,j-l[i]);k++){ chmax(dp[j+1][k/2],dp[j][k]+(k/2)*c[j+1]); } } } cout<<dp[n+m][0]; }

面对*3300的数据结构题,我只能望而兴叹

第一步都想错了。。。

考虑只有 0 / 1 0/1 0/1的情况,答案就是最长的 01 01 01段长度除以 2 2 2

对于这道题,任取一个 Mid \text{Mid} Mid,将 ≤ Mid \le\text{Mid} Mid的位置看成 0 0 0 > Mid >\text{Mid} >Mid的位置看成 1 1 1,只要取遍所有的 Mid \text{Mid} Mid,求答案的最大值就是第一问的答案。

对于第二问,考虑 Mid \text{Mid} Mid从小到大取遍时,第一次出现 0 0 0就是这个位置所对应的权值。好像有更聪明的方法,但是我不清楚

接下来的一步比较运气。考虑其差分序列,有一些比较好的性质,比如一段连续的 1 1 1就代表了一段 01 01 01交错的区间,所以第一问的答案就是连续段 1 1 1长度的最大值 / 2 /2 /2;对于 0 0 0相当于是不动点,因此我们用 set \text{set} set维护这些不动点的位置,然后区间覆盖即可。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

还是想的有点久,希望下次能更快一点

常数大的离谱,而且调的也有点久,考场上估计时间就不够了吧。

#include<bits/stdc++.h> #define pb push_back #define fi first #define se second #define ll long long #define db double #define inf 0x3f3f3f3f using namespace std; const int N=5e5+5; int n,a[N],b[N],lsh[N],cnt,res[N],ans; set<int>s,s2; int get(int x){return lower_bound(lsh+1,lsh+1+cnt,x)-lsh;} vector<int>point[N]; struct node{ int tag,val; }t[N<<2]; void add(int p,int Mid){ if(!t[p].tag){ t[p].tag=1,t[p].val=Mid; } } void pushdown(int p){ if(t[p].tag){ add(p<<1,t[p].val),add(p<<1|1,t[p].val); } } int qry(int p,int l,int r,int x){ if(l==r){ return t[p].val?t[p].val:inf; } int mid=l+r>>1; pushdown(p); return x<=mid?qry(p<<1,l,mid,x):qry(p<<1|1,mid+1,r,x); } void upd3(int p,int l,int r,int ql,int qr,int Mid){ if(ql>qr)return; if(ql<=l&&r<=qr){ add(p,Mid); return; } pushdown(p); int mid=l+r>>1; if(ql<=mid)upd3(p<<1,l,mid,ql,qr,Mid); if(mid<qr)upd3(p<<1|1,mid+1,r,ql,qr,Mid); } void upd2(int l,int r,int Mid){ auto it=s.lower_bound(l-1),it2=s.lower_bound(r); if(++it!=it2){ return; } ans=max(ans,(r-l)/2); if(r-l&1){ if(b[l]==0){ upd3(1,1,n,l+1,(l+r)/2,Mid); } else{ upd3(1,1,n,(l+r)/2+1,r-1,Mid); } } else{ if(b[l]==0){ upd3(1,1,n,l+1,r-1,Mid); } } } void del(int x){ auto it=s.lower_bound(x),it2=it; s2.erase(x); if(++(it2=it)!=s.end()){ s2.insert(*it2); } if(it!=s.begin()){ s2.insert(*--(it2=it)); } s.erase(x); } void ins(int x){ auto it=s.lower_bound(x),it2=it; if(it!=s.end()){ s2.insert(*it); } if(it!=s.begin()){ s2.insert(*--(it2=it)); } s.insert(x); s2.insert(x); } void upd(int x){ b[x]=0; if(x<n&&b[x]==b[x+1]){ ins(x); } if(x<n&&b[x]!=b[x+1]){ del(x); } if(x>1&&b[x]==b[x-1]){ ins(x-1); } if(x>1&&b[x]!=b[x-1]){ del(x-1); } } void mark(int x,int y){ if(!res[x])res[x]=y; } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n;for(int i=1;i<=n;i++)cin>>a[i],lsh[i]=a[i]; sort(lsh+1,lsh+1+n),cnt=unique(lsh+1,lsh+1+n)-lsh-1; for(int i=1;i<=n;i++)b[i]=1; for(int i=1;i<=n;i++)point[get(a[i])].pb(i); for(int i=0;i<=n;i++)s.insert(i); for(int i=1;i<=cnt;i++){ s2.clear(); for(int j=0;j<point[i].size();j++)upd(point[i][j]); int l=-1; for(auto it:s2){ if(b[it]==0){ mark(it,lsh[i]),mark(it+1,lsh[i]); } if(~l){ upd2(l+1,it,lsh[i]); } l=it; } } cout<<ans<<"\n"; for(int i=1;i<=n;i++){ if(i==1||i==n)cout<<a[i]<<" "; else cout<<min(qry(1,1,n,i),res[i])<<" "; } }

算是比较友好的一道题

首先对于每个点,设 a i a_i ai 0 0 0表示 c i < c f a i c_i<c_{fa_i} ci<cfai a i a_i ai 1 1 1表示 c i > c f a i c_i>c_{fa_i} ci>cfai,如果已知 { a i } \{a_i\} {ai},那么再二分答案 Mid \text{Mid} Mid,显然我们可以树 d p dp dp判断是否合法。

显然 { a i } , { c i } \{a_i\},\{c_i\} {ai},{ci}需要同时考虑并转移,这也是这道题麻烦的地方。

我们发现对于 { a i } \{a_i\} {ai}有两种限制:
1.1 1.1 1.1 a i = a f a i a_i=a_{fa_i} ai=afai
1.2 1.2 1.2 对于两个兄弟 i , j i,j i,j a i ≠ a j a_i\ne a_j ai=aj(这是 L C A LCA LCA处的限制)

那么我们定义, d p i dp_i dpi表示当 a i a_i ai 0 0 0时的 c i c_i ci的最小值,显然将整颗子树的 { a i } \{a_i\} {ai}取反就能得到 a i a_i ai 1 1 1 c i c_i ci的最大值为 Mid − d p i + 1 \text{Mid}-dp_i+1 Middpi+1。因此,知道了儿子的 { a i } \{a_i\} {ai}就能推出 d p i dp_i dpi

观察上述限制,由于我们固定了 a i = 0 a_i=0 ai=0,因此对于第一类限制可以直接解决了。对于第二类限制,我们可以进行一个分组。对于没有固定的组,可以让其中一组全为 0 0 0,而另一组全为 1 1 1,那么我们观察,所有 [ x + 1 , Mid − y ] ∪ [ y + 1 , Mid − x ] [x+1,\text{Mid}-y]\cup [y+1,\text{Mid}-x] [x+1,Midy][y+1,Midx]的交当中最小的那个点就是我们的答案。因为这两个区间是对称的,所以可以做到线性转移。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

唉,不知不觉这道题就写了半天,我真是太菜了

代码非常丑,不过好歹还是补完了

很难评价这道题,因为码量确实有点大,考场上无论如何都打不出来吧

其实我也在想,遇到这样复杂一点的问题,以我的码力真的能够完成吗?

常数大的离谱。

#include<bits/stdc++.h> #define pb push_back #define fi first #define se second #define ll long long #define db double #define inf 0x3f3f3f3f using namespace std; const int N=5e5+5; int n,m,U[N],V[N],f[N][20],dep[N]; vector<int>g[N]; int Lca(int x,int y){ if(dep[x]<dep[y])swap(x,y); for(int i=19;i>=0;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i]; if(x==y)return x; for(int i=19;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i]; return f[x][0]; } void dfs(int u,int topf){ dep[u]=dep[topf]+1; f[u][0]=topf;for(int i=1;i<=19;i++)f[u][i]=f[f[u][i-1]][i-1]; for(auto v:g[u]){ if(v!=topf){ dfs(v,u); } } } int fa[N*2],cf[N],cf2[N],dp[N]; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} void unionset(int x,int y){ int u=find(x),v=find(y); if(u!=v)fa[u]=v; } void dfs2(int u,int topf){ for(auto v:g[u]){ if(v!=topf){ dfs2(v,u),cf[u]+=cf[v],cf2[u]+=cf2[v]; } } if(cf[u]){ unionset(u,topf),unionset(u+n,topf+n); } } int ok,task,vis[N],c[2],C[N]; int state[N]; vector<int>G[N]; vector<int>point[2]; void dfs4(int u,int col){ c[col]=max(c[col],dp[u]),C[u]=col,vis[u]=task; point[col].pb(u); //cout<<u<<"\n"; for(auto v:G[u]){ if(vis[v]!=task){ dfs4(v,col^1); } } } struct node{ int c[2]; vector<int>point[2]; }now; vector<node>vec; int dfs3(int u,int topf,int Mid){ state[u]=0; int L=1,R=Mid,L2=1,R2=Mid; for(auto v:g[u]){ if(v!=topf&&!dfs3(v,u,Mid)){ return 0; } } for(auto v:g[u]){ //fixed if(v!=topf&&cf2[v]&&vis[v]!=task&&cf[v]){ c[0]=c[1]=0; point[0].clear(),point[1].clear(); dfs4(v,0); c[0]++,c[1]++; if(c[0]+c[1]>Mid+1)return 0; for(auto x:point[1])state[x]^=1; L2=max(L2,c[0]),R2=min(R2,Mid-c[1]+1); } } vec.clear(); for(auto v:g[u]){ if(v!=topf&&cf2[v]&&vis[v]!=task){ c[0]=c[1]=0; point[0].clear(),point[1].clear(); dfs4(v,0); c[0]++,c[1]++; if(c[0]+c[1]>Mid+1)return 0; if(c[0]>c[1])swap(c[0],c[1]),swap(point[0],point[1]); now.c[0]=c[0],now.c[1]=c[1]; now.point[0]=point[0],now.point[1]=point[1]; vec.pb(now); if(Mid-c[1]+1>=Mid/2+1){ L=max(L,c[0]),R=min(R,Mid-c[0]+1); } else{ L=max(L,c[0]),R=min(R,Mid-c[1]+1); } } } if(max(L,L2)<=min(R,R2)){ dp[u]=max(L,L2); } else if(max(Mid-R+1,L2)<=min(Mid-L+1,R2)){ dp[u]=max(Mid-R+1,L2); } else{ return 0; } for(auto &v:vec){ if(v.c[0]<=dp[u]&&dp[u]<=Mid-v.c[1]+1){ for(auto &x:v.point[1]){ state[x]^=1; } } else{ assert(v.c[1]<=dp[u]&&dp[u]<=Mid-v.c[0]+1); for(auto &x:v.point[0]){ state[x]^=1; } } } return 1; } int ans[N]; void dfs5(int u,int topf,int Mid){ if(state[u])ans[u]=dp[u]; else ans[u]=Mid-dp[u]+1; for(auto v:g[u]){ if(v!=topf){ state[v]^=state[u],dfs5(v,u,Mid); } } } int check(int Mid){ task++; return dfs3(1,0,Mid); } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>m;for(int i=1;i<=2*n;i++)fa[i]=i; for(int i=1;i<n;i++){ int x,y; cin>>x>>y,g[x].pb(y),g[y].pb(x); } dfs(1,0); for(int i=1;i<=m;i++){ int u,v;cin>>u>>v; int lca=Lca(u,v); cf[u]++,cf[v]++; cf2[u]++,cf2[v]++,cf2[lca]-=2; for(int j=19;j>=0;j--){ if(dep[f[u][j]]>dep[lca])u=f[u][j]; if(dep[f[v][j]]>dep[lca])v=f[v][j]; } cf[u]--,cf[v]--; if(u!=lca&&v!=lca){ unionset(u,v+n),unionset(u+n,v); G[u].pb(v),G[v].pb(u); } } dfs2(1,0); for(int i=1;i<=n;i++){ if(find(i)==find(n+i)){ cout<<-1<<"\n"; return 0; } } int l=1,r=n,res=0; while(l<=r){ int mid=l+r>>1; if(check(mid))res=mid,r=mid-1; else l=mid+1; } cout<<res<<"\n"; check(res); dfs5(1,0,res); for(int i=1;i<=n;i++)cout<<ans[i]<<" "; }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530010.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(7)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示