CF827D Best Edge Weight[最小生成树+树剖/LCT/(可并堆/set启发式合并+倍增)]
题意:一张图求每条边边权最多改成多少可以让所有MST都包含这条边。
这题还是要考察Kruskal的贪心过程。
先跑一棵MST出来。然后考虑每条边。
如果他是非树边,要让他Kruskal的时候被选入,必须要让他连的两个点$u,v$连通之前被选上,也就是说,必须得小于MST上$u,v$路径中的至少一条边,那么让他小于最大的那条(减一)即可。
如果他是树边,那么考虑如果删去他,他连接的两点如果要连通,可否用其他边替换。发现一定可以用经过这条边的非树边替换他,且会使用最小的一条非树边作为新的MST的边。所以只要找到路径覆盖这条边的所有非树边的最小的减一即可。
综上,我们需要做树上链查询$\max$,链取$\min$的操作,并且这两个操作相互独立。我一开始写了一个倍增,结果欠思考,在取$\min$操作上卡住了。。所以我重新写了一个树剖分别维护两个信息。复杂度$O(n\log^2 n)$,吊打单$\log$。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define dbg(x) cerr << #x << " = " << x <<endl 8 #define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl 9 using namespace std; 10 typedef long long ll; 11 typedef double db; 12 typedef pair<int,int> pii; 13 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 14 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 15 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 16 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 17 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 18 template<typename T>inline T read(T&x){ 19 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 20 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 21 } 22 const int N=2e5+7,INF=0x3f3f3f3f; 23 struct thxorz{int to,nxt,w;}G[N<<1]; 24 int Head[N],tot=1; 25 int ans[N],used[N]; 26 int n,m; 27 inline void Addedge(int x,int y,int z){ 28 G[++tot].to=y,G[tot].nxt=Head[x],Head[x]=tot,G[tot].w=z; 29 G[++tot].to=x,G[tot].nxt=Head[y],Head[y]=tot,G[tot].w=z; 30 } 31 int fa[N],topfa[N],dep[N],son[N],sum[N],pos[N],A[N],cnt; 32 #define y G[j].to 33 void dfs1(int x,int f){ 34 fa[x]=f;dep[x]=dep[G[f^1].to]+1;sum[x]=1;int tmp=-1; 35 for(register int j=Head[x];j;j=G[j].nxt)if((j^1)^f)dfs1(y,j),sum[x]+=sum[y],MAX(tmp,sum[y])&&(son[x]=y); 36 } 37 void dfs2(int x,int topf){ 38 topfa[x]=topf;pos[x]=cnt,A[cnt++]=G[fa[x]].w;if(!son[x])return;dfs2(son[x],topf); 39 for(register int j=Head[x];j;j=G[j].nxt)if((j^1)^fa[x]&&y^son[x])dfs2(y,y); 40 } 41 #undef y 42 struct segment_tree{ 43 int maxv[N<<2],cov[N<<2],tag[N<<2]; 44 #define lc i<<1 45 #define rc i<<1|1 46 segment_tree(){memset(cov,0x3f,sizeof cov),memset(tag,0x3f,sizeof tag);} 47 inline void pushdown(int i){ 48 if(tag[i]<INF){MIN(tag[lc],tag[i]),MIN(tag[rc],tag[i]);MIN(cov[lc],tag[i]),MIN(cov[rc],tag[i]);tag[i]=INF;} 49 } 50 void Build(int i,int L,int R){ 51 if(L==R){maxv[i]=A[L];return;} 52 Build(lc,L,L+R>>1),Build(rc,(L+R>>1)+1,R);maxv[i]=_max(maxv[lc],maxv[rc]); 53 } 54 int UPD(int i,int L,int R,int ql,int qr,int val){ 55 if(ql<=L&&qr>=R){MIN(cov[i],val);MIN(tag[i],val);return maxv[i];} 56 int mid=L+R>>1,ret=0;pushdown(i); 57 if(ql<=mid)MAX(ret,UPD(lc,L,mid,ql,qr,val)); 58 if(qr>mid)MAX(ret,UPD(rc,mid+1,R,ql,qr,val)); 59 cov[i]=_min(cov[lc],cov[rc]);return ret; 60 } 61 int Query_cover(int i,int L,int R,int x){ 62 if(L==R)return cov[i]; 63 int mid=L+R>>1;pushdown(i); 64 if(x<=mid)return Query_cover(lc,L,mid,x); 65 return Query_cover(rc,mid+1,R,x); 66 } 67 }T; 68 69 struct wphorz{ 70 int u,v,w,id; 71 inline bool operator <(const wphorz&A)const{return w<A.w;} 72 }e[N]; 73 struct dsu{ 74 int anc[N]; 75 inline void Clear(){for(register int i=1;i<=n;++i)anc[i]=i;} 76 inline int Find(int x){return anc[x]==x?x:anc[x]=Find(anc[x]);} 77 }S; 78 inline void Kruskal(){ 79 sort(e+1,e+m+1);S.Clear();//for(register int i=1;i<=m;++i)printf("%d %d %d\n",e[i].u,e[i].v,e[i].w); 80 for(register int i=1;i<=m;++i)if(S.Find(e[i].u)^S.Find(e[i].v)) 81 S.anc[S.anc[e[i].u]]=S.anc[e[i].v],Addedge(e[i].u,e[i].v,e[i].w),used[i]=1; 82 } 83 inline int qry_and_upd(int x,int y,int val){ 84 int ret=0; 85 while(topfa[x]^topfa[y]){ 86 if(dep[topfa[x]]<dep[topfa[y]])_swap(x,y); 87 MAX(ret,T.UPD(1,1,n-1,pos[topfa[x]],pos[x],val)); 88 x=G[fa[topfa[x]]^1].to; 89 } 90 if(dep[x]>dep[y])_swap(x,y); 91 return _max(ret,x==y?0:T.UPD(1,1,n-1,pos[x]+1,pos[y],val)); 92 } 93 inline void process(){ 94 for(register int i=1;i<=m;++i)if(!used[i])ans[e[i].id]=qry_and_upd(e[i].u,e[i].v,e[i].w)-1; 95 for(register int i=1;i<=m;++i)if(used[i]){//printf("%d %d\n",e[i].id,e[i].w); 96 if(dep[e[i].u]<dep[e[i].v])_swap(e[i].u,e[i].v); 97 ans[e[i].id]=T.Query_cover(1,1,n-1,pos[e[i].u]); 98 ans[e[i].id]<INF?ans[e[i].id]--:ans[e[i].id]=-1; 99 }//puts(""); 100 for(register int i=1;i<=m;++i)printf("%d ",ans[i]);puts(""); 101 } 102 103 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 104 read(n),read(m); 105 for(register int i=1;i<=m;++i)read(e[i].u),read(e[i].v),read(e[i].w),e[i].id=i; 106 Kruskal();dfs1(1,0),dfs2(1,1); 107 T.Build(1,1,n-1);process(); 108 return 0; 109 }
事实上,倍增那种方法也不是不可以,只不过取$\min$操作只要离线在节点上打标记,$x,y$处标记插入值,$lca$处标记删除之,然后用set维护最小值,dfs自下而上启发式合并更新即可。复杂度$O(n\log^2 n)$。还可以把set改成可并堆,然后删除的话用懒惰删除法(见lyd书),然后做到一个$\log$。
另外一种方法,直接LCT。
总结:掌握好Kruskal本质是关键。