[SDOI2019] 世界地图
题意:
给你一个$n\times m$的网格图,Q组询问,每次询问删掉第$[l_{i},r_{i}]$列的所有点后这张图的MST(最小生成树)。
$1\leq n\leq 100,1\leq m,Q\leq 10000$。
题解:
平时我们求最小生成树都是用Kruskal:把边排个序依次加入,用并查集合并。
但其实还有一种LCT的做法:不排序随便加边,每加到一条边$(u,v,w)$:
- 如果u,v不连通,直接加;
- 如果u,v连通且路径$u\rightarrow v$上最大边权$\leq w$,直接跳过;
- 否则,加入$(u,v,w)$并删除路径$u\rightarrow v$上的最大权边。
回到这道题,如果能维护每个前后缀的MST,那么每次询问就可以直接用上面那个做法合并两个MST。
但有一个问题:如果维护MST上的所有点那空间和时间都会爆炸。
注意到其实我们需要的只是第1列的点与第m列的点两两之间的边权最大值,那么我们可以把其他点缩到边上,建一个虚树。
考虑前后缀要维护什么东西,首先需要维护原图MST的边权之和sum,然后维护一棵将原图MST中所有关键点拿出来建的虚树。
虚树的每条边是原图MST中这两个关键点之间边权的最大值,点数是$O(n)$级别的。
(维护树其实就是维护边集,维护点集会让你看起来像一个nt)
考虑怎么合并,发现其实不用写LCT,可以把这两种做法混起来用:
把两个虚树的边和新边扔到一起跑Kruskal,然后重新dfs一遍建虚树,最后把sum加减一下就是答案了。
于是现在问题只剩下怎么维护前后缀了,发现维护时就是把$MST[1,i-1]$和$MST[i,i]$合并,那么每次把第i列的点作为关键点,套用上面的做法即可。
复杂度$O(nm\log{n})$,写起来细节比较多。注意重复利用空间,不要随便建一棵树又不清空。
套路:
- 求最小生成树$\rightarrow$Kruskal或LCT。
- 维护点之间的和/最值且复杂度过大$\rightarrow$只保留关键点$\rightarrow$建虚树。
代码:
#include<bits/stdc++.h> #define maxn 205 #define maxm 20005 #define inf 0x7fffffff #define ll long long #define rint register int #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; ll hd[maxn*maxm],to[maxm],nxt[maxm]; ll n,m,cnt,mk[maxn*maxm],dep[maxn*maxm]; ll siz[maxn*maxm],F[maxn*maxm],cst[maxm]; unsigned int SA,SB,SC; int lim; struct edge{ ll u,v,w; bool operator<(const edge b){return w<b.w;} }cr[maxn][maxm],li[maxn][maxm]; struct MST{ ll sum; vector<edge> E; }pre[maxm],suf[maxm],res,tp; int L,R; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline int getweight(){ SA^=SA<<16,SA^=SA>>5,SA^=SA<<1; unsigned int t=SA; SA=SB,SB=SC,SC^=t^SA; return SC%lim+1; } inline ll find(ll x){return (F[x]==x)?x:(F[x]=find(F[x]));} inline ll id(ll x,ll y){return (x-1)*m+y;} inline edge make_edge(ll u,ll v,ll w){ edge ans; ans.u=u,ans.v=v,ans.w=w; return ans; } inline void make_tree(ll x,ll opt){ tp.sum=0,tp.E.clear(); for(rint i=1;i<=n;i++){ if(i<n) tp.E.push_back(li[i][x]),tp.sum+=li[i][x].w; if(opt==1 && x!=1) tp.E.push_back(cr[i][x-1]),tp.sum+=cr[i][x-1].w; if(opt==-1 && x!=m) tp.E.push_back(cr[i][x]),tp.sum+=cr[i][x].w; } } inline void addedge(ll u,ll v,ll w){ to[++cnt]=v,cst[cnt]=w,nxt[cnt]=hd[u],hd[u]=cnt; to[++cnt]=u,cst[cnt]=w,nxt[cnt]=hd[v],hd[v]=cnt; } inline void dfs(ll u,ll fa){ siz[u]=mk[u]; for(rint i=hd[u];i;i=nxt[i]){ ll v=to[i],w=cst[i]; if(v==fa) continue; dfs(v,u),siz[u]+=(siz[v]>0); } if(siz[u]>=2) mk[u]=1; } inline void Dfs(ll u,ll fa,ll las,ll rt){ if(mk[u] && u!=rt){ res.E.push_back(make_edge(las,u,dep[u])); las=u,dep[u]=0; } for(rint i=hd[u];i;i=nxt[i]){ ll v=to[i],w=cst[i]; if(v==fa) continue; dep[v]=max(dep[u],w),Dfs(v,u,las,rt); } } inline MST merge(MST a,MST b,ll x,ll y){ tp.sum=0,tp.E.clear(),res.E.clear(),cnt=0; for(rint i=0;i<a.E.size();i++) tp.E.push_back(a.E[i]),res.sum-=a.E[i].w; for(rint i=0;i<b.E.size();i++) tp.E.push_back(b.E[i]),res.sum-=b.E[i].w; sort(tp.E.begin(),tp.E.end()); for(rint i=0;i<tp.E.size();i++){ ll u=tp.E[i].u,v=tp.E[i].v; if(u%m==x%m || u%m==y%m) mk[u]=1; else mk[u]=0; if(v%m==x%m || v%m==y%m) mk[v]=1; else mk[v]=0; F[u]=u,F[v]=v,hd[u]=hd[v]=0; } for(rint i=0;i<tp.E.size();i++){ ll u=tp.E[i].u,v=tp.E[i].v,w=tp.E[i].w; ll t1=find(u),t2=find(v); if(t1!=t2) F[t2]=t1,res.sum+=w,addedge(u,v,w); } dfs(x,0),dep[x]=0,Dfs(x,0,x,x); return res; } int main(){ n=read(),m=read(),SA=read(),SB=read(),SC=read(),lim=read(); for(rint i=1;i<=n;i++) for(rint j=1;j<=m;j++){ if(j<m) cr[i][j]=make_edge(id(i,j),id(i,j+1),getweight()); else cr[i][j]=make_edge(id(i,j),id(i,1),getweight()); } for(rint i=1;i<n;i++) for(rint j=1;j<=m;j++) li[i][j]=make_edge(id(i,j),id(i+1,j),getweight()); memset(mk,0,sizeof(mk)); memset(hd,0,sizeof(hd)); for(rint i=1;i<=n;i++) for(rint j=1;j<=m;j++) F[id(i,j)]=id(i,j); pre[0].sum=suf[m+1].sum=0; for(rint j=1;j<=m;j++) make_tree(j,1),res.sum=pre[j-1].sum+tp.sum,pre[j]=merge(pre[j-1],tp,1,j); memset(mk,0,sizeof(mk)); memset(hd,0,sizeof(hd)); for(rint i=1;i<=n;i++) for(rint j=1;j<=m;j++) F[id(i,j)]=id(i,j); for(rint j=m;j>=1;j--) make_tree(j,-1),res.sum=suf[j+1].sum+tp.sum,suf[j]=merge(suf[j+1],tp,m,j); memset(mk,0,sizeof(mk)); memset(hd,0,sizeof(hd)); for(rint i=1;i<=n;i++) for(rint j=1;j<=m;j++) F[id(i,j)]=id(i,j); ll q=read(); while(q--){ ll l=read()-1,r=read()+1; tp=suf[r]; for(rint i=1;i<=n;i++) tp.E.push_back(cr[i][m]),tp.sum+=cr[i][m].w; res.sum=pre[l].sum+tp.sum; merge(tp,pre[l],1,m); printf("%lld\n",res.sum); } return 0; }