联通性这东西学一次忘一次,只能背板子了。
原理?太困难了算了吧,板子也不难背。
真想学原理去网上找找别的博客吧。
考 tarjan 原理就爆了。
还有,tarjan 的复杂度是 的。
模板
求割边模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include"bits/stdc++.h" #define re register #define int long long #define pii pair<int,int> #define fi first #define se second using namespace std; const int maxn=1e5+10; int n,m,cnt=1,ans,tim; int head[maxn]; int dfn[maxn],low[maxn]; struct edge{ int to,nxt; }e[maxn<<1]; vector<pii> g; inline void add(int u,int v){ e[++cnt]={v,head[u]}; head[u]=cnt; } inline void tarjan(int u,int eid){ dfn[u]=low[u]=++tim; for(re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(!dfn[v]){ tarjan(v,i); low[u]=min(low[u],low[v]); if(low[v]>dfn[u]) g.push_back({u,v}); } else if(i!=(eid^1)) low[u]=min(low[u],dfn[v]); } } signed main(){ #ifndef ONLINE_JUDGE freopen("1.in","r",stdin); freopen("1.out","w",stdout); #endif ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin>>n>>m; for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u); for(re int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,0); sort(g.begin(),g.end()); for(auto x:g) cout<<x.fi<<" "<<x.se<<'\n'; return 0; }
|
求割点模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include"bits/stdc++.h" #define re register #define int long long using namespace std; const int maxn=1e5+10,maxm=1e5+10; int n,m,cnt,tim,ans; int head[maxn]; int dfn[maxn],low[maxn]; bitset<maxn> cut; struct node{ int to,nxt; }e[maxm<<1]; vector<int> g; inline void add(int u,int v){ e[++cnt]={v,head[u]}; head[u]=cnt; } inline void tarjan(int u,int fa){ int son=0; dfn[u]=low[u]=++tim; for(re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(!dfn[v]){ ++son; tarjan(v,u); low[u]=min(low[u],low[v]); if(u!=fa&&low[v]>=dfn[u]) g.push_back(u); } else if(v!=fa) low[u]=min(low[u],dfn[v]); } if(u==fa&&son>=2) g.push_back(u); } signed main(){ #ifndef ONLINE_JUDGE freopen("1.in","r",stdin); freopen("1.out","w",stdout); #endif ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin>>n>>m; for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u); for(re int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,i); sort(g.begin(),g.end()); g.erase(unique(g.begin(),g.end()),g.end()); for(auto x:g) cout<<x<<'\n'; return 0; }
|
缩点模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| #include"bits/stdc++.h" #define re register #define int long long #define pii pair<int,int> #define fi first #define se second using namespace std; const int maxn=1e5+10,maxm=1e5+10; int n,m,cnt,top,tim,scc; int head[maxn]; int dfn[maxn],low[maxn],stk[maxn]; int col[maxn],f[maxn]; int in[maxn]; bitset<maxn> vis; vector<pii> g; struct edge{ int to,nxt; }e[maxm]; inline void add(int u,int v){ e[++cnt]={v,head[u]}; head[u]=cnt; } inline void tarjan(int u){ dfn[u]=low[u]=++tim; stk[++top]=u,vis[u]=1; for(re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(vis[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]){ ++scc;int x; do{ x=stk[top--]; col[x]=scc; vis[x]=0; }while(x!=u); } } inline void clear(){ cnt=0; memset(head,0,sizeof head); } inline void topo(){ queue<int> q; for(re int i=1;i<=scc;++i) if(!in[i]) q.push(i); while(!q.empty()){ int u=q.front(); q.pop(); for(re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(!(--in[v])) q.push(v); } } } signed main(){ #ifndef ONLINE_JUDGE freopen("1.in","r",stdin); freopen("1.out","w",stdout); #endif ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin>>n>>m; for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),g.push_back({u,v}); for(re int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); clear(); for(auto x:g) if(col[x.fi]!=col[x.se]) add(col[x.fi],col[x.se]),++in[col[x.se]]; topo(); return 0; }
|
求点双模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include"bits/stdc++.h" using namespace std; #define re register #define int long long const int maxn=5e5+10,maxm=2e6+10; int n,m,cnt,tot,top,tim; int head[maxn]; int dfn[maxn],low[maxn]; int stk[maxn]; bitset<maxn> vis; vector<int> g[maxn]; struct edge{ int to,nxt; }e[maxm<<1]; inline void add(int u,int v){ e[++cnt]={v,head[u]}; head[u]=cnt; } inline void tarjan(int u,int fa){ int son=0; dfn[u]=low[u]=++tim; stk[++top]=u; for(re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(!dfn[v]){ ++son; tarjan(v,u); low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]){ ++tot;int x; g[tot].push_back(u); do{ x=stk[top--]; g[tot].push_back(x); }while(x!=v); } } else if(v!=fa) low[u]=min(low[u],dfn[v]); } if(u==fa&&!son) g[++tot].push_back(u); } signed main(){ #ifndef ONLINE_JUDGE freopen("1.in","r",stdin); freopen("1.out","w",stdout); #endif ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin>>n>>m; for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u); for(re int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,i); cout<<tot<<'\n'; for(re int i=1;i<=tot;++i){ cout<<g[i].size()<<" "; for(auto j:g[i]) cout<<j<<" "; cout<<'\n'; } return 0; }
|
求边双模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include"bits/stdc++.h" #define re register #define int long long using namespace std; const int maxn=5e5+10,maxm=2e6+10; int n,m,cnt=1,tot,tim; int head[maxn]; int dfn[maxn],low[maxn]; int col[maxn]; bitset<maxm<<1> vis; vector<int> g[maxn]; struct edge{ int to,nxt; }e[maxm<<1]; inline void add(int u,int v){ e[++cnt]={v,head[u]}; head[u]=cnt; } inline void tarjan(int u,int eid){ dfn[u]=low[u]=++tim; for(re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(!dfn[v]){ tarjan(v,i); low[u]=min(low[u],low[v]); if(low[v]>dfn[u]) vis[i]=vis[i^1]=1; } else if(i!=(eid^1)) low[u]=min(low[u],dfn[v]); } } inline void dfs(int u,int id){ col[u]=id; g[id].push_back(u); for(re int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(col[v]||vis[i]) continue; dfs(v,id); } } signed main(){ #ifndef ONLINE_JUDGE freopen("1.in","r",stdin); freopen("1.out","w",stdout); #endif ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin>>n>>m; for(re int i=1,u,v;i<=m;++i) cin>>u>>v,add(u,v),add(v,u); for(re int i=1;i<=n;++i) if(!dfn[i]) tarjan(i,0); for(re int i=1;i<=n;++i){ if(!col[i]) dfs(i,++tot); } cout<<tot<<'\n'; for(re int i=1;i<=tot;++i){ cout<<g[i].size()<<" "; for(auto j:g[i]) cout<<j<<" "; cout<<'\n'; } return 0; }
|
常见技巧
有一些无向图上的问题需要先缩个点/边双然后变成树上问题。
有一些有向图上的问题需要先缩个点然后变成 DAG,然后拓扑序上搞事情。
但一般来说难点都在缩完以后搞得那些东西上,例如 dp,贪心,数据结构啥的。
点不能重复:缩点双。一条边最多属于一个点双。
边不能重复:缩边双。一个点最多属于一个边双。
所以把环缩成点要缩边双。
例题
下面选的基本都是一些简单板子题。
缩点板子。
首先判断无解,然后缩点,然后把入度为 0 的点的权值拿了就行。
一个强连通分量构成的点的权值为其内所有点的最小权值。
把环缩成点要缩边双。
然后变成一颗树,树上距离随便求。
首先缩点,那么第一问答案就是入度为 0 的点的个数(和上面那个题一样)。
第二问要求最少加几条边使得 DAG 变成强连通。
我们贪心的想,肯定要让无出度点连向无入度点。那么连的边数就是 。
注意特判本来就是强连通的情况。
缩点以后变为 DAG 上最长路,dp 一下即可。
经典结论题。
求最少添加多少条边,使得图变成边双连通。
缩完点双后,统计入度和出度,答案为:(入度和出度之和为 1 的点+1)/2
板子。
强连通分量内部不需要价值,所以缩点以后 DAG 上最短路即可。
板子。
强连通分量显然可以全拿,缩点以后 DAG 上最长路即可,注意只能在是酒吧的点取答案。
板子。
缩点以后 DAG 上最长路即可,注意还得求个最大值。
怎么降蓝了?在 WLFS 做的时候还是紫。
如果图建好了,那么它是板子,缩点以后 DAG 上最长路即可。
但难点就在建图。
直接建是 的,肯定不行。
但是每一行的横门可以相互到达,每一列的纵门也可以相互到达。
对于优化建边有个常见套路:建虚点,然后把边都连向虚点。
这样边数就是 的了。
边双缩点后树形 dp。
但是我不会这个 dp。
摆。
分情况讨论。
若 不是割点,那么它会和剩下 个点不连通。
若 是割点,那么显然会得到若干联通块( 这个点自己也算个连通块)。
那么答案就是 。
求割点时顺便统计一下子树大小即可。
缩点以后变成树形背包板子。
为什么缩完是树?因为题目保证只依赖一个物品。
把图建出来以后就是[USACO5.3] 校园网Network of Schools 的第二问。
好玩的博弈论。
先不考虑博弈,只看这张图能不能全变白,可以发现:如果一个强连通分量里有异色点,那就不能。
所以可以先缩点,顺便判断无解,此时我们得到一张 DAG。
如果你思考一会,会发现这个游戏是很难有人获胜的。
除非一步就能赢,否则对方可以一直模仿你的操作使你赢不了。
所以可以猜到:只有一步能赢的时候才有人能赢,否则平局。
那么考虑怎么一步赢。
Alice 是很好考虑的,因为只能操作一次,所以如果遇到一个黑点,那么往后走就不能出现白点。
如果搜完以后有没搜到的黑点,那么就寄了。
Bob 比较复杂。
- 若存在至少一个入度为 0 的点为白点,那么 Bob 获胜当且仅当所有点都是白点。因为 Alice 可以让其中一个点变黑,Bob 为了赢必须把这个点弄回来。
- 若所有入度为 0 的点均为黑点,如果只有一个那 Alice 肯定赢。如果有两个,那 Bob 肯定赢。否则平局。
- 只有两个点,一黑一白,且黑点指向白点,则 Bob 必胜。
综上,Bob 必胜的情况共三种:
- 两个孤立黑点。
- 两个点,一黑一白,且黑点指向白点。
- 所有点均为白点。
因为边不可以重复经过,所以缩边双。
变成一棵树后就很简单了,每次覆盖一条路径,求被覆盖过的点的点权和。
事实上都不用树剖,树上差分即可。