神仙题
题目大意:
有一张\(n\)个点\(m\)条边的无向联通图,每次修改一条边的边权,问每次修改之后这张图的最小生成树权值和
话说是不是\(cdq\)题目都可以用什么数据结构莽过去啊……
这道题目确实是\(cdq\)好题
\(cdq\)分治一定要处理多维偏序问题吗?并不是,它的核心思想是一个用子问题计算对另一个子问题的贡献
我们将这道题按照时间轴来分治,那么显然一个子问题对另一个子问题是存在贡献的
我们将整张图上的边进行分类:
在当前分治区间内涉及到修改的边称为动态边
其他边称为静态边
我们在分治过程中,动态边显然会随着区间的缩小而减少,那么我们怎么来处理静态边呢?
仔细考虑,我们根本不可以发现,静态边可以分为三类:
\(1.\)无用边:肯定不会作为最小生成树中的边
对于这类边,我们可以直接扔掉
\(2.\)必要边:必作为生成树中的边
对于这种边,我们可以直接把边权计入答案,然后把边连接的两个点用并查集合并到一起
\(3.\)其他边
没什么办法了,直接传给子区间吧
按照这种方法,每分治到一个更小的区间,其他区间的动态边就会变成静态边,当我们分治到\(l==r\)时,所有的边都会变为静态边,那么其他边将不存在,必要边贡献的答案就是答案了
现在我们有两个问题:
1:如何求出前两类边?
求无用边,我们可以将所有动态边的边权设置为\(inf\),然后跑\(Kruskal\),最后没有被用到的静态边都是无用边
求必要边,我们可以将所有动态边的边权设置为\(-inf\),还跑\(Kruskal\),最后被用到的静态边都是必要边
正确性好像挺显然的(雾
2:时间复杂度?
假设当前区间内有\(k\)条动态边,那么除去必要边和无用边之后,最多留下\(k+1\)个点和\(2k\)条边
由于\(k\)每次降低一倍,所以边数和点数都是都每次减少接近一倍,复杂度约为\(O(nlog^2n)\)
注意我们每次操作要先求必要边再求无用边复杂度才对!!!
我们注意这道题,它的其他子问题对当前子问题的贡献并不是按照一般\(cdq\)的方法,而是在进入当前区间之前已经将贡献计算好了
#include<bits/stdc++.h> using namespace std; namespace red{ #define int long long #define mid ((l+r)>>1) #define lowbit(x) ((x)&(-x)) inline int read() { int x=0;char ch,f=1; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } const int N=1e5+10; int n,m,ask; struct edge { int x,y,val,opt; inline bool operator < (const edge &t) const { return val<t.val; } }a[N]; struct moved { int x,y; }; struct node { int num,val,ret; }q[N]; struct dsu { int f[N],str[N]; struct blue {int x,y;}; stack<blue> st; inline void init() { for(int i=1;i<=n;++i) f[i]=i,str[i]=1; } inline int find(int k) { return f[k]==k?k:find(f[k]); } inline void merge(int x,int y) { int tx=find(x),ty=find(y); if(tx==ty) return; if(str[tx]<str[ty]) swap(tx,ty); str[tx]+=str[ty]; f[ty]=tx; blue tmp; tmp.x=tx,tmp.y=ty; st.push(tmp); } inline void del() { blue now=st.top(); st.pop(); f[now.y]=now.y; str[now.x]-=str[now.y]; } inline void clear(int bot)//可撤销并查集 { while(st.size()>bot) del(); } }s1,s;//s全局 int ret[30]; //reduction无用边 //contraction必须边 vector<edge> eg[30],tr;//存每一层静态边和临时静态边 vector<moved> rose;//存当前动态边 int bot[30]; bool book[N]; inline void push_down(int dep) { tr.clear(); for(int i=0;i<eg[dep].size();++i) tr.push_back(eg[dep][i]); sort(tr.begin(),tr.end()); s1.clear(0); ret[dep+1]=ret[dep]; for(int i=0;i<rose.size();++i) s1.merge(rose[i].x,rose[i].y); rose.clear(); for(int tx,ty,i=0;i<tr.size();++i)//求必要边 { tx=s1.find(tr[i].x),ty=s1.find(tr[i].y); if(tx==ty) continue; tr[i].opt=1; s1.merge(tr[i].x,tr[i].y); s.merge(tr[i].x,tr[i].y); ret[dep+1]+=tr[i].val; } s1.clear(0);//撤销 for(int i=0;i<tr.size();++i)//求无用边 { if(tr[i].opt) continue; if(s.find(tr[i].x)==s.find(tr[i].y)||s1.find(tr[i].x)==s1.find(tr[i].y)) { tr[i].opt=-1; continue; } s1.merge(tr[i].x,tr[i].y); } s1.clear(0);//撤销 eg[dep+1].clear(); for(int tx,ty,i=0;i<tr.size();++i) { if(tr[i].opt) continue; tx=s.find(tr[i].x),ty=s.find(tr[i].y); if(tx==ty) continue; edge tmp; tmp.x=tx,tmp.y=ty,tmp.val=tr[i].val,tmp.opt=0; eg[dep+1].push_back(tmp); } return; } inline void cdq(int l,int r,int dep) { bot[dep]=s.st.size(); if(l==r) { edge tmp; tmp.x=s.find(a[q[r].num].x),tmp.y=s.find(a[q[r].num].y);//底层 tmp.val=q[r].val,tmp.opt=0; a[q[r].num].val=q[r].val;//注意这里,直接更改 eg[dep].push_back(tmp); push_down(dep); q[r].ret=ret[dep+1]; s.clear(bot[dep-1]); return; } for(int i=l;i<=mid;++i) book[q[i].num]=1;//递归左边时,右边的动态变静态 for(int i=mid+1;i<=r;++i) { if(book[q[i].num]) continue; edge tmp; tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y); tmp.val=a[q[i].num].val,tmp.opt=0; eg[dep].push_back(tmp); } for(int i=l;i<=mid;++i)//左边的动态存进去 { moved tmp; tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y); rose.push_back(tmp); } push_down(dep);//下放 for(int i=mid+1;i<=r;++i) { if(book[q[i].num]) continue; eg[dep].pop_back(); } for(int i=l;i<=mid;++i) book[q[i].num]=0;//回溯 cdq(l,mid,dep+1); for(int i=0;i<eg[dep].size();++i) eg[dep][i].opt=0;//去掉标记 for(int i=mid+1;i<=r;++i) book[q[i].num]=1; for(int i=l;i<=mid;++i)//左边的动态变静态 { if(book[q[i].num]) continue; edge tmp; tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y); tmp.val=a[q[i].num].val,tmp.opt=0; eg[dep].push_back(tmp); } for(int i=mid+1;i<=r;++i)//右边的动态放进去 { book[q[i].num]=0; moved tmp; tmp.x=s.find(a[q[i].num].x),tmp.y=s.find(a[q[i].num].y); rose.push_back(tmp); } push_down(dep); cdq(mid+1,r,dep+1); s.clear(bot[dep-1]);//撤销并查集 } inline void main() { n=read(),m=read(),ask=read(); s1.init(),s.init(); for(int i=1;i<=m;++i) a[i].x=read(),a[i].y=read(),a[i].val=read(); for(int i=1;i<=ask;++i) { q[i].num=read(),q[i].val=read(); book[q[i].num]=1; } for(int i=1;i<=m;++i) { if(book[i]) continue; eg[1].push_back(a[i]); } for(int i=1;i<=ask;++i) book[q[i].num]=0; cdq(1,ask,1); for(int i=1;i<=ask;++i) printf("%lld\n",q[i].ret); } } signed main() { red::main(); return 0; }
来源:https://www.cnblogs.com/knife-rose/p/12045917.html