割边 割点 双联通 缩点 欧拉路 (算法进阶指南)上的知识点 省选之前最后的复习。
tarjan这个人好强啊 首先(%%%)tarjan算法的核心我想就应该 dfn 及low 这两个数组了。
以前学的时候感触不深现在 我明白了 真正的含义 dfn 就是 时间戳 而low 就是返祖的时候计算的回溯值。
深刻理解这两个数组之后 上面的问题就比较容易解决了。
首先是割边 。定义 把整张联通的图分成两部分的那一条边也就是说 去掉这条边 整张图被分为两部分了。
判定法则 自己想想 首先是 一条割边肯定是不能存在在环里的 让一个点的儿子随便走只要不顺着刚刚从父亲的这条边爬下来的边走回去 也就是说自己会形成的一棵子树
此时 这条从父亲到儿子的边 就是割边(我的理解)至于代码实现 是一个较为另类的 low[tn]>dfn[x] 那么这条边是割边 为什么没有等于 发生重边之时。这条边就一定不是割边了所以只有大于号 考虑 low[tn]能被 哪些东西更新 首先被自己的儿子 的 low值更新因为儿子能跑到的地方这个父亲也能跑到。
还能被 然后就是返祖边来更新当然判断割边他不能跑从父亲跑来的边,于是用返祖边来更新 当前节点。
那么考虑 用dfn 来更新呢还是用low值来更新呢 我想了很久。。。我想 :
low[lca]<=dfn[lca] 所以大体上都是可以判断割边的 不过深究的话 应该是一样的 不过作为low 的真正含义回溯值 它再往前跑是没有作用的因为返祖边的到达组先后dfn就已经够用了 所以 直接用dfn 进而更新即可。
值得一提的是割边的成对变换 比较有意思。大体上割边就这么多内容了。
//#include<bits/stdc++.h> #include<iostream> #include<iomanip> #include<cmath> #include<cstdio> #include<ctime> #include<queue> #include<stack> #include<vector> #include<cctype> #include<utility> #include<algorithm> #include<cstring> #include<string> #include<map> #include<set> #include<bitset> #include<deque> #include<cstdlib> #define INF 2147483646 #define ll long long #define db double #define R register #define l(i) t[i].l #define r(i) t[i].r #define v(i) t[i].v #define v1(i) t[i].v1 using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } inline void put(int x) { x<0?putchar('-'),x=-x:0; int num=0;char ch[70]; while(x)ch[++num]=x%10+'0',x/=10; num==0?putchar('0'):0; while(num)putchar(ch[num--]); putchar(' ');return; } const int MAXN=5002; int n,m,top,num; int vis[MAXN<<1],mark[MAXN],dfn[MAXN],low[MAXN]; int lin[MAXN<<1],nex[MAXN<<1],ver[MAXN<<1],w[MAXN<<1],len=1; struct wy { int x,y; friend int operator <(const wy x,const wy y) { return x.x==y.x?x.y<y.y:x.x<y.x; } }t[MAXN]; inline void add(int x,int y) { ver[++len]=y; nex[len]=lin[x]; lin[x]=len; w[len]=x; } inline int min(int x,int y){return x>y?y:x;} inline int max(int x,int y){return x>y?x:y;} inline void tarjan(int x,int last) { mark[x]=1;dfn[x]=low[x]=++num; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(!mark[tn]) { tarjan(tn,i); low[x]=min(low[x],low[tn]); if(low[tn]>dfn[x])vis[i]=vis[i^1]=1; } else if(i!=(last^1))low[x]=min(low[x],dfn[tn]); } return; } int main() { //freopen("1.in","r",stdin); n=read();m=read(); for(int i=1;i<=m;++i) { int x,y; x=read();y=read(); add(x,y);add(y,x); } tarjan(1,0); for(int i=2;i<=len;++i) { if(vis[i]) { t[++top].x=min(w[i],ver[i]); t[top].y=max(w[i],ver[i]); i++; } } sort(t+1,t+1+top); for(int i=1;i<=top;++i)printf("%d %d ",t[i].x,t[i].y); return 0; }
然后是割点 割点的定义 把图中一个点去掉后整张图被分成了两部分 但是需注意一条链的两个起点都不是割点。
因为去掉他们 还是一张图 这就推出了我们需要特判第一个点 如果只有一个儿子那么其不是割点。
判定法则 的话 利用dfn 和low 这两个数组如何实现呢 我想是 割点想一下 还是儿子在其自己的子树中乱跑 然后不到上面的话那就父亲是割点了(不是各节点的话)
那么这种情况 是可以拿儿子到父亲的返祖边来更新儿子的low值的但是却不能拿父亲的low值来更新儿子的low值跟上面不一样。
而其中的判定法则是 low[tn]>=dfn[x] 其实 对于无向图 加上上面的那句话
这句话 可以改变 low[tn]==dfn[x] 的话x就是割点 (不要光看书要思考啊) 那么条件被找出 即可实现了。至于根节点我们只需判断其实割点且有两个儿子以上即可。
//#include<bits/stdc++.h> #include<iostream> #include<iomanip> #include<cmath> #include<cstdio> #include<ctime> #include<queue> #include<stack> #include<vector> #include<cctype> #include<utility> #include<algorithm> #include<cstring> #include<string> #include<map> #include<set> #include<bitset> #include<deque> #include<cstdlib> #define INF 2147483646 #define ll long long #define db double using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void put(int x) { x<0?putchar('-'),x=-x:0; int num=0;char ch[70]; while(x)ch[++num]=x%10+'0',x/=10; num==0?putchar('0'):0; while(num)putchar(ch[num--]); putchar(' ');return; } const int MAXN=60002; int n,cnt,x,y,ans,root=1; int cut[MAXN],dfn[MAXN],low[MAXN]; int lin[MAXN<<1],nex[MAXN<<1],ver[MAXN<<1],len; inline int min(int x,int y){return x>y?y:x;} inline void add(int x,int y) { ver[++len]=y; nex[len]=lin[x]; lin[x]=len; } inline void tarjan(int x) { dfn[x]=low[x]=++cnt; int flag=0; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(!dfn[tn]) { tarjan(tn); low[x]=min(low[x],low[tn]); if(dfn[x]==low[tn]) { flag++; if(x!=root||flag>1)cut[x]==0?++ans:0,cut[x]=1; } } else low[x]=min(low[x],dfn[tn]); } } int main() { //freopen("1.in","r",stdin); n=read(); while(scanf("%d%d",&x,&y)==2){add(x,y);add(y,x);} for(int i=1;i<=n;++i)if(!dfn[i]){root=i;tarjan(root);} put(ans); for(int i=1;i<=n;++i)if(cut[i])put(i); return 0; }
注意图有的时候是不连通的 这个坑到我了 十几次 千万注意。。。
完全跟着算法进阶指南走吧似乎有道例题。
由于是复习 再写一遍这道题。 复习一下不过要加快进度了。
所有town联通说明整张图联通去掉某个点那么首先减小的对数是 2*(n-1)个点 (x,y)is different with (y,x);
特殊之处是 如果去掉割点的话就另当别论了 总之仔细一点统计答案即可。
//#include<bits/stdc++.h> #include<iostream> #include<iomanip> #include<cmath> #include<cstdio> #include<ctime> #include<queue> #include<stack> #include<vector> #include<cctype> #include<utility> #include<algorithm> #include<cstring> #include<string> #include<map> #include<set> #include<bitset> #include<deque> #include<cstdlib> #define INF 2147483646 #define ll long long #define db double using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline ll read() { ll x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } inline void put(ll x) { x<0?putchar('-'),x=-x:0; ll num=0;char ch[70]; while(x)ch[++num]=x%10+'0',x/=10; num==0?putchar('0'):0; while(num)putchar(ch[num--]); putchar(' ');return; } const ll MAXN=500002; ll n,m,num; ll ans[MAXN],dfn[MAXN],low[MAXN],cut[MAXN]; ll sz[MAXN];//s[i]表示以i为根节点的子树大小 ll lin[MAXN<<1],nex[MAXN<<1],ver[MAXN<<1],len; inline void add(ll x,ll y) { ver[++len]=y; nex[len]=lin[x]; lin[x]=len; } inline ll min(ll x,ll y){return x>y?y:x;} void tarjan(ll x) { dfn[x]=low[x]=++num; sz[x]=1;ll flag=0,sum=0; for(ll i=lin[x];i;i=nex[i]) { ll tn=ver[i]; if(!dfn[tn]) { tarjan(tn); sz[x]+=sz[tn]; low[x]=min(low[x],low[tn]); if(low[tn]==dfn[x]) { flag++; if(x!=1||flag>1) { cut[x]=1;sum+=sz[tn]; ans[x]+=sz[tn]*(n-sz[tn]-1); } } } else low[x]=min(low[x],dfn[tn]); } if(cut[x])ans[x]+=(n-sum-1)*sum; return; } int main() { //freopen("1.in","r",stdin); n=read();m=read(); for(ll i=1;i<=m;++i) { ll x,y; x=read();y=read(); add(x,y);add(y,x); } tarjan(1); for(ll i=1;i<=n;++i)put(ans[i]+(n-1)*2); return 0; }
然后就到了双联通分量:
如果一张图上不存在割点 说明都是环(这个环不太彻底) 这张图称之为 点双联通图。
如果这张图上不存在割边 说明都是环(彻底都是环)这张图称之为 边双联通图。
至于判定 边双联通分量是 只 图上任意两点都在 同一个环内。
点双联通分量比较复杂 暂不讨论省选要退役了。
此题求点双联通分量 缩点 LCA 并查集即可 。
比较困难。。。至于路径压缩 手动压缩 即可。 必须使用倍增求LCA 向上爬。
//#include<bits/stdc++.h> #include<iostream> #include<iomanip> #include<cmath> #include<cstdio> #include<ctime> #include<queue> #include<stack> #include<vector> #include<cctype> #include<utility> #include<algorithm> #include<cstring> #include<string> #include<map> #include<set> #include<bitset> #include<deque> #include<cstdlib> #define INF 2147483646 #define ll long long #define db double using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } inline void put(int x) { x<0?putchar('-'),x=-x:0; int num=0;char ch[70]; while(x)ch[++num]=x%10+'0',x/=10; num==0?putchar('0'):0; while(num)putchar(ch[num--]); putchar(' ');return; } const int MAXN=200002; int n,m,query,T,cnt,len=1,dcc,ans,clen,t,h,num; int dfn[MAXN],low[MAXN],depth[MAXN],f[MAXN][20]; int ver[MAXN<<1],nex[MAXN<<1],lin[MAXN<<1]; int cver[MAXN<<1],cnex[MAXN<<1],clin[MAXN<<1]; int bridge[MAXN<<1],c[MAXN],fa[MAXN],q[MAXN]; inline int min(int x,int y){return x>y?y:x;} inline int getfather(int x){return x==fa[x]?x:fa[x]=getfather(fa[x]);} inline void swap(int &x,int &y){int t=x;x=y;y=t;} inline void add(int x,int y) { ver[++len]=y; nex[len]=lin[x]; lin[x]=len; } inline void cadd (int x,int y) { cver[++clen]=y; cnex[clen]=clin[x]; clin[x]=clen; } inline void tarjan(int x,int last) { dfn[x]=low[x]=++cnt; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(!dfn[tn]) { tarjan(tn,i); low[x]=min(low[x],low[tn]); if(low[tn]>dfn[x])bridge[i]=bridge[i^1]=1; } else if(i!=(last^1))low[x]=min(low[x],dfn[tn]); } return; } inline void dfs(int x) { c[x]=dcc; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(c[tn]||bridge[i])continue; dfs(tn); } } inline void bfs() { h=t=0; q[++t]=1;depth[1]=1; while(h++<t) { int te=q[h]; for(int i=clin[te];i;i=cnex[i]) { int tn=cver[i]; if(depth[tn])continue; depth[tn]=depth[te]+1; f[tn][0]=te; for(int j=1;j<=T;++j)f[tn][j]=f[f[tn][j-1]][j-1]; q[++t]=tn; } } } inline int LCA(int x,int y) { if(depth[x]>depth[y])swap(x,y); for(int i=T;i>=0;--i) if(depth[f[y][i]]>=depth[x])y=f[y][i]; if(x==y)return x; for(int i=T;i>=0;--i) if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i]; return f[x][0]; } int main() { //freopen("1.in","r",stdin); while(1) { n=read();m=read();++num; if(n==0&&m==0)break; printf("Case %d: ",num); T=(int)(log(n*1.0)/log(2*1.0))+1; for(int i=1;i<=n;++i)fa[i]=i; memset(lin,0,sizeof(lin)); memset(clin,0,sizeof(clin)); memset(depth,0,sizeof(depth)); memset(c,0,sizeof(c)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(f,0,sizeof(f)); memset(bridge,0,sizeof(bridge)); len=1;clen=0;dcc=0;ans=0;cnt=0; for(int i=1;i<=m;++i) { int x,y; x=read();y=read(); add(x,y);add(y,x); } for(int i=1;i<=n;++i)if(!dfn[i])tarjan(i,0); //for(int i=1;i<=len;++i)if(bridge[i])++ans;put(ans/2); for(int i=1;i<=n;++i)if(!c[i])++dcc,dfs(i); //put(dcc); for(int i=2;i<=len;i=i+2) { int x=ver[i]; int y=ver[i^1]; if(c[x]==c[y])continue; cadd(c[x],c[y]);cadd(c[y],c[x]);//ans++; } bfs();ans=dcc-1; query=read(); for(int i=1;i<=query;++i) { int x,y; x=read();y=read(); if(c[x]==c[y]){put(ans);continue;} x=c[x];y=c[y]; int lca=LCA(x,y); x=getfather(x); y=getfather(y); while(depth[x]>depth[lca]) { fa[x]=f[x][0]; ans--; x=getfather(x); } while(depth[y]>depth[lca]) { fa[y]=f[y][0]; ans--; y=getfather(y); } put(ans); } puts(""); } return 0; }
下面是 有像图的强联通分量 互相能到达的那堆东西就是强联通分量。
求出强联通分量即可。
//#include<bits/stdc++.h> #include<iostream> #include<iomanip> #include<cmath> #include<cstdio> #include<ctime> #include<queue> #include<stack> #include<vector> #include<cctype> #include<utility> #include<algorithm> #include<cstring> #include<string> #include<map> #include<set> #include<bitset> #include<deque> #include<cstdlib> #define INF 2147483646 #define ll long long #define db double using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } inline void put(int x) { x<0?putchar('-'),x=-x:0; int num=0;char ch[70]; while(x)ch[++num]=x%10+'0',x/=10; num==0?putchar('0'):0; while(num)putchar(ch[num--]); putchar(' ');return; } const int MAXN=5000002,maxn=10002; int n,len,cnt,num,top,maxx,maxx1; int dfn[maxn],low[maxn],c[maxn],s[maxn],vis[maxn],ru[maxn],chu[maxn]; int ver[MAXN],nex[MAXN],lin[MAXN]; inline int min(int x,int y){return x>y?y:x;} inline int max(int x,int y){return x>y?x:y;} inline void add(int x,int y) { ver[++len]=y; nex[len]=lin[x]; lin[x]=len; } inline void tarjan(int x) { dfn[x]=low[x]=++num; s[++top]=x;vis[x]=1; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(!dfn[tn]) { tarjan(tn); low[x]=min(low[x],low[tn]); } else if(vis[tn])low[x]=min(low[x],dfn[tn]); } if(dfn[x]==low[x]) { ++cnt; int y; do { y=s[top--]; c[y]=cnt; } while(y!=x); } } int main() { //freopen("1.in","r",stdin); n=read(); for(int i=1;i<=n;++i) { int x; while(1) { x=read(); if(!x)break; add(i,x); } } for(int i=1;i<=n;++i)if(!dfn[i])tarjan(i); //put(cnt); for(int i=1;i<=n;++i) { for(int j=lin[i];j;j=nex[j]) { int tn=ver[j]; if(c[tn]==c[i])continue; ru[c[tn]]++;chu[c[i]]++; } } for(int i=1;i<=cnt;++i) { if(!ru[i])maxx++; if(!chu[i])maxx1++; } put(maxx); put(max(maxx,maxx1)); return 0; }
完成!祝我以及我的学长HAOI 2019 RP++!!!