HDU6200 mustedge mustedge mustedge
不用看题就知道这是和什么tarjan、缩点或桥一类有关的题。
谁让他取题目叫一个mustedge还连续写3次的(哦,似乎是因为那个比赛的题目都是这个画风)
必须的边 》必须要经过的边 》 桥。
主要是动态维护问题,幸好只有加边操作。
建dfs树之后,在dfs树上加边其实就是让dfs树上一些边没有用了。(就这一点我想了很久才想到,真是zz啊)
建dfs树之后,在dfs树上加边其实就是让dfs树上一些边没有用了。(就这一点我想了很久才想到,真是zz啊)
那么可以用树剖来维护这个东西(这不是显然的么)
然后你就非常开心地打了一个树剖,一开始树上边的权值都为1,然后每次区间修改(把一个区间所有边的权值变为0)
然后你非常开心地交了。
然后T了。
这道题裸的树剖是不行di。$10^6$专卡你。
需要一些小技巧。
我们知道,我们最多有$10^6$个操作,但是我们也最多只有$10^6$个点,也就是$10^6-1$条边。
那么我们在频繁清零的时候,很多边是重复清零了的。
如果我们每个边最多清一次0,就不会很慢。
那就。。。并查集,维护每条边上方第一个没有清零的边(有用的边)。
我们按dfs序建树状数组。然后每次修改就是向上跳到第一个还有没有清零边,然后log时间修改,就是单点修改区间查询。
所以说修改的总时间复杂度不超过$nlogn$,而裸的树剖还要多带一个$log$(求lca一个,修改一个)。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<vector> using namespace std; const int maxn=1e5+10; int T,n,m,Q; int aa,ff;char cc; int read() { aa=0;cc=getchar();ff=1; while(cc<'0'||cc>'9') { if(cc=='-') ff=-1; cc=getchar(); } while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa*ff; } struct Edge{ int u,v; }; vector<Edge> G; int fir[maxn],nxt[2*maxn],to[2*maxn],e=0; void add(int x,int y) { to[++e]=y;nxt[e]=fir[x];fir[x]=e; to[++e]=x;nxt[e]=fir[y];fir[y]=e; } int f[maxn]; int find(int x) {return x==f[x]? x:f[x]=find(f[x]);} int fa[maxn],size[maxn],son[maxn],dep[maxn]; void dfs1(int pos) { size[pos]=1;int y,z; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==fa[pos]) continue; fa[z]=pos;dep[z]=dep[pos]+1; dfs1(z); size[pos]+=size[z]; if(size[z]>size[son[pos]]) son[pos]=z; } } int id[maxn],top[maxn],cnt; void dfs2(int pos,int tp) { id[pos]=++cnt; top[pos]=tp; if(!son[pos]) return; dfs2(son[pos],tp); int y,z; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==fa[pos]||to[y]==son[pos]) continue; dfs2(z,z); } } int sz[maxn]; int q(int l,int r) { int rs=0;l--; while(r) { rs+=sz[r]; r-=(r&-r); } while(l) { rs-=sz[l]; l-=(l&-l); } return rs; } void chge(int l,int r){ int rr; for(r=find(r);r>=l;r=f[r]) { rr=r; while(rr<=n){ sz[rr]--; rr+=(rr&-rr); } f[r]=find(r-1); } } void get_lca(int x,int y,int p) { int rs=0; while(top[x]!=top[y]) { if(dep[top[x]]<dep[top[y]]) swap(x,y); if(p) rs+=q(id[top[x]],id[x]); else chge(id[top[x]],id[x]); x=fa[top[x]]; } if(x!=y) { if(dep[x]<dep[y]) swap(x,y); if(p) rs+=q(id[y]+1,id[x]); else chge(id[y]+1,id[x]); } if(p) printf("%d\n",rs); } int main() { T=read(); int x,y,z,xx,yy; for(int qaq=1;qaq<=T;++qaq) { n=read();m=read(); G.clear(); e=0; cnt=0; memset(fir,0,sizeof(fir)); memset(son,0,sizeof(son)); memset(fa,0,sizeof(fa)); for(int i=1;i<=n;++i) f[i]=i; for(int i=1;i<=m;++i) { x=read();y=read(); xx=find(x);yy=find(y); if(xx!=yy) add(x,y),f[xx]=yy; else G.push_back(Edge{x,y}); } Q=read(); dep[1]=1; dfs1(1); dfs2(1,1); for(int i=1;i<=n;++i) f[i]=i; for(int i=1;i<=n;++i) sz[i]=(i&-i); z=G.size(); for(int i=0;i<z;++i) get_lca(G[i].u,G[i].v,0); printf("Case #%d:\n",qaq); for(int i=1;i<=Q;++i) { x=read();y=read();z=read(); get_lca(y,z,x-1); } } return 0; }
除此之外,也可以用树状数组维护每个点到根的距离,然后每次修改就是把子树的区间-1,这样就是区间修改单点查询了。
不过还是要用并查集维护每条边上方第一个没有清零的边(有用的边)。
所以说这道题的重点就是避免重复的修改浪费时间,用并查集维护上方第一个没有清零的边。
弱者就是会被欺负呀