本套餐包含

材料来源于洛谷题单:【图论】Tarjan 入门
给几碟配菜:
参考文章1
参考文章2
参考文章3
参考文章4
大部分都是板子题嘻嘻嘻嘻
SCC强连通分量
定义
极大的强连通子图,即对于该子图,任意的两点之间均有路径可以使其互相到达。
模板题
0001
洛谷 B3609 [图论与代数结构 701] 强连通分量
一句话题解
没得啥,就是使用Tj求强连通分量
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int dfn[10010],low[10010],scc[10010];
int cnt,scnt;
stack <int> s;
bool pd[10010];
bool vis[10010];
int h[10010],to[100010],nxt[100010],tot;
void add(int x,int y)
{tot++;to[tot]=y;nxt[tot]=h[x];h[x]=tot;
}
void tj(int x)
{dfn[x]=low[x]=++cnt;vis[x]=1;s.push(x);for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]==1){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x]){int y;scnt++;do{y=s.top();vis[y]=0;s.pop();scc[y]=scnt; }while(x!=y);}
}
int main()
{int n,m;cin>>n>>m;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;add(x,y);}for(int i=1;i<=n;i++){if(!dfn[i]){tj(i);}}cout<<scnt<<endl;memset(pd,0,sizeof(pd));for(int i=1;i<=n;i++){int x=scc[i];if(pd[x]==1){continue;}pd[x]=1;for(int j=1;j<=n;j++){if(scc[j]==x){cout<<j<<" ";}}cout<<endl;}return 0;
}
0010
洛谷 P2863 [USACO06JAN] The Cow Prom S
一句话题解
Tj求强连通分量过程中计数,最后统计答案
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int h[10010],to[50010],nxt[50010],tot;
int low[10010],dfn[10010],scc[10010];
int scnt,cnt;
int res[10010];
bool vis[10010];
stack <int> s;
void add(int x,int y)
{tot++;to[tot]=y;nxt[tot]=h[x];h[x]=tot;
}
void tj(int x)
{dfn[x]=low[x]=++cnt;s.push(x);vis[x]=1;for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x]){scnt++;int y;do{y=s.top();s.pop();scc[y]=scnt;res[scnt]++;vis[y]=0;}while(x!=y);}
}
int main()
{int n,m;cin>>n>>m;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;add(x,y);}for(int i=1;i<=n;i++){if(!dfn[i]){tj(i);}}int ans=0;for(int i=1;i<=scnt;i++){if(res[i]>1){ans++;}}cout<<ans;return 0;
}
0011
洛谷 P1726 上白泽慧音
一句话题解
Tj求强连通分量过程中计数,然后比较大小,从SCC数组中求答案
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int h[5010],to[50010],nxt[50010],tot;
int dfn[5010],low[5010],scc[5010];
int cnt,scnt;
stack<int> s;
bool vis[5010];
int js[5010];
int minx[5010];
void add(int x,int y)
{tot++;to[tot]=y;nxt[tot]=h[x];h[x]=tot;
}
void tj(int x)
{dfn[x]=low[x]=++cnt;vis[x]=1;s.push(x);for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x]){int y;scnt++;do{y=s.top();vis[y]=0;s.pop();scc[y]=scnt; }while(x!=y);}
}
int main()
{int n,m;cin>>n>>m;for(int i=1;i<=m;i++){int x,y,op;cin>>x>>y>>op;if(op==1){add(x,y);}else{add(x,y);add(y,x);}}for(int i=1;i<=n;i++){if(!dfn[i]){tj(i); } }int maxx=0;int maxs=0;for(int i=1;i<=n;i++){js[scc[i]]++;if(minx[scc[i]]==0){minx[scc[i]]=i;}if(js[scc[i]]>maxx||(js[scc[i]]==maxx&&minx[maxs]>minx[scc[i]])){maxx=js[scc[i]];maxs=scc[i];}}cout<<maxx<<endl; for(int i=1;i<=n;i++){if(scc[i]==maxs){cout<<i<<" "; } } return 0;
}
烧烤题
0001
P1407 [国家集训队] 稳定婚姻
题面不太体面。。但好题,Tj和2-SAT都能写
一句话题解
我们将可能旧情复燃或处于婚姻状态下的两个人用有向边连起来
考虑如何是不稳定的婚姻,当其几对人的关系构成一条环(强连通分量)时无论断掉哪一条边,都会使这几个人重新连接
考虑细节,如何连边:发现题面还没开放到出现同行链的情况一定是异性相连,则可以妻子->丈夫->前女友(即相当于 前男友->妻子->丈夫)
然后Tj求scc然后判断一下妻子和丈夫是否在同一个强连通分量里。
(tip:钦定\(x\)表示第\(x\)对关系的妻子身份,\(x+n\)表示第\(x\)对关系的丈夫子身份,然后使用map对应记录一下即可)
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int h[8010],to[30010],nxt[30010],tot;
map<string,int>mp;
int dfn[8010],low[8010],scc[8010];
int scnt,cnt;
bool vis[8010];
stack <int> s;
int n,m;
void add(int x,int y)
{tot++;to[tot]=y;nxt[tot]=h[x];h[x]=tot;
}
void tj(int x)
{dfn[x]=low[x]=++cnt;s.push(x);vis[x]=1;for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]); } else if(vis[y]){low[x]=min(low[x],dfn[y]);}} if(low[x]==dfn[x]){scnt++;int y;do{y=s.top();s.pop();vis[y]=0;scc[y]=scnt;}while(y!=x);}
}
int main()
{cin>>n;for(int i=1;i<=n;i++){string s1,s2;cin>>s1>>s2;mp[s1]=i;mp[s2]=i+n;add(i,i+n); }cin>>m;for(int i=1;i<=m;i++){string s1,s2;cin>>s1>>s2;add(mp[s2],mp[s1]);}for(int i=1;i<=2*n;i++){if(!dfn[i]){tj(i); } } for(int i=1;i<=n;i++){if(scc[i]==scc[i+n]){cout<<"Unsafe"<<endl;}else{cout<<"Safe"<<endl;}}return 0;
}
缩点
定义
将一个图缩点之后,会得到一个DAG,然后就可以在DAG做DP,拓扑等操作
模板题
0001
洛谷 P3387 【模板】缩点
一句话题解
没得啥,就是使用Tj求强连通分量然后缩点+拓扑排序求最大值。
(tips:本人习惯使用链式前向星建两个图并且在原图上记一个from数组存一条有向边的出点)
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int h[10010],to[100010],nxt[100010],from[100010],tot;
int val[10010];
int v[10010];
int h_[10010],to_[100010],nxt_[100010],tot_;
int in[10010];
int dfn[10010],low[10010],scc[10010];
int scnt,cnt;
int dis[10010];
bool vis[10010];
stack <int> s;
int n,m;
void add(int x,int y)
{tot++;to[tot]=y;from[tot]=x;nxt[tot]=h[x];h[x]=tot;
}
void add_(int x,int y)
{tot_++;to_[tot_]=y;nxt_[tot_]=h_[x];h_[x]=tot_;
}
void tj(int x)
{low[x]=dfn[x]=++cnt;s.push(x);vis[x]=1;for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]){low[x]=min(low[x],dfn[y]);}}if(dfn[x]==low[x]){int y;scnt++;do{y=s.top();scc[y]=scnt;val[scnt]+=v[y];s.pop();vis[y]=0;}while(x!=y);}
}
int topu()
{queue<int> q;for(int i=1;i<=scnt;i++){dis[i]=val[i];if(!in[i]){q.push(i);}}while(!q.empty()){int x=q.front();q.pop();for(int i=h_[x];i;i=nxt_[i]){int y=to_[i];dis[y]=max(dis[y],dis[x]+val[y]);in[y]--;if(in[y]==0){q.push(y);}}}int ans=0;for(int i=1;i<=scnt;i++){ans=max(ans,dis[i]);}return ans;
}
int main()
{cin>>n>>m;for(int i=1;i<=n;i++){cin>>v[i];}for(int i=1;i<=m;i++){int x,y;cin>>x>>y;add(x,y);}for(int i=1;i<=n;i++){if(!dfn[i]){tj(i);}}for(int i=1;i<=m;i++){int x=from[i],y=to[i];if(scc[x]!=scc[y]){add_(scc[x],scc[y]);in[scc[y]]++;}}cout<<topu();return 0;
}
烧烤题
0001
洛谷 P10944 Going from u to v or from v to u?
一句话题解
考虑对于一对在同一个强连通分量里的点,其一定能到达(对答案没有影响)
所以就可以进行一个缩点的操作。
考虑在得到的DAG上进行答案的判定。
以下给出两种方法
- 跑\(n\)次dfs判连通性
一点也不优美! - 拓扑排序,使用dp记录DAG上最长的一条链,若其节点数等于强连通分量的个数,则输出“Yes”。(正确性显然)
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
long long dfn[1010],low[1010],scc[1010];
long long scnt,cnt;
bool vis[1010];
int dp[1010];
stack <long long> s;
long long h[1010],to[12010],from[12010],nxt[12010],tot;
long long h1[1010],to1[12010],nxt1[12010],tot1;
long long in[1010];
void add(long long x,long long y)
{tot++;to[tot]=y;from[tot]=x;nxt[tot]=h[x];h[x]=tot;
}
void add1(long long x,long long y)
{tot1++;to1[tot1]=y;nxt1[tot1]=h1[x];h1[x]=tot1;
}
void tj(long long x)
{dfn[x]=low[x]=++cnt;s.push(x);vis[x]=1;for(long long i=h[x];i;i=nxt[i]){long long y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x]){long long y;scnt++;do{y=s.top();s.pop();scc[y]=scnt;vis[y]=0;}while(x!=y);}
}
bool topu()
{queue <long long> q;for(long long i=1;i<=scnt;i++){if(in[i]==0){q.push(i);dp[i]=1;}}int maxx=1;while(!q.empty()){long long x=q.front();q.pop();for(long long i=h1[x];i;i=nxt1[i]){long long y=to1[i];dp[y]=max(dp[y],dp[x]+1);maxx=max(maxx,dp[y]);in[y]--;if(in[y]==0){q.push(y);}}}if(maxx==scnt){return 1;}else{return 0;}
}
int main()
{long long T;cin>>T;while(T--){memset(vis,0,sizeof(vis));memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));memset(h,0,sizeof(h));memset(h1,0,sizeof(h1));memset(dp,0,sizeof(dp));memset(in,0,sizeof(in));memset(scc,0,sizeof(scc));tot=0;tot1=0;cnt=0;scnt=0;long long n,m;cin>>n>>m;for(long long i=1;i<=m;i++){long long x,y;cin>>x>>y;add(x,y);}for(long long i=1;i<=n;i++){if(!dfn[i]){tj(i);}}for(long long i=1;i<=m;i++){long long x=from[i];long long y=to[i];if(scc[x]!=scc[y]){add1(scc[x],scc[y]);in[scc[y]]++;}}if(topu()){cout<<"Yes"<<endl;}else{cout<<"No"<<endl;}}return 0;
}
0010
洛谷 P2746 [IOI 1996 / USACO5.3] 校园网 Network of Schools
一句话题解
显然,处在强连通分量里的点都可以互传文件,所以先做一个缩点操作。
然后让我们分开考虑问题一和问题二
问题一:
对于每个强连通分量,若它的入度为0,则没有其他点可以和它传文件,需要下发.
答案为$$入度为0的强连通分量的个数$$
问题二
考虑入度或出度为0的强连通分量,它不给其他点传文件或没有点给它传文件,无法使DAG强连通,所以考虑在每对入度出度为0的强连通分量连边,若其中有一方剩余,则随便找一个强连通分量连边即可
答案为$$max(入度为0的强连通分量个数,出度为0的强连通分量个数)$$
tips:若只有一个强连通分量,则特判输出1,0即可。该题#3为此特殊构造
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int dfn[110],low[110],scc[110];
int scnt,cnt;
bool vis[110];
stack <int> s;
int rd[110];
int cd[110];
int h[110],nxt[10010],from[10010],to[10010],tot;
void add(int x,int y)
{tot++;to[tot]=y;from[tot]=x;nxt[tot]=h[x];h[x]=tot;
}
void tj(int x)
{dfn[x]=low[x]=++cnt;s.push(x);vis[x]=1;for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x]){int y;scnt++;do{y=s.top();s.pop();vis[y]=0;scc[y]=scnt;}while(y!=x);}
}
int main()
{int n;cin>>n;for(int i=1;i<=n;i++){int x;cin>>x;while(x!=0){add(i,x);cin>>x;}}for(int i=1;i<=n;i++){if(!dfn[i]){tj(i);}}if(scnt==1){cout<<1<<endl<<0;return 0;}for(int i=1;i<=tot;i++){int x=from[i];int y=to[i];if(scc[x]!=scc[y]){rd[scc[y]]++;cd[scc[x]]++;}}int ans1=0,ans2=0;for(int i=1;i<=scnt;i++){if(rd[i]==0){ans1++;}if(cd[i]==0){ans2++;}}cout<<ans1<<endl<<max(ans1,ans2);return 0;
}
0011
P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G
经典题
一句话题解
仍然先考虑强连通分量,发现对于一个强连通分量里的几只牛,其一定互相喜欢。即明星奶牛所在的强连通分量内的所有奶牛皆是明星奶牛。
然后是经典项目“缩点”,然后我们就可以拾取到一个DAG。
考虑如何判断一个强连通分量是否是明星强连通分量:
- 若得到的DAG是连通的(即没有一个点既没有入度又没有出度)
当其有且仅有一个强连通分量的出度为0,则其为明星强连通分量,此时答案为该强连通分量的大小。反之,因为其没有一个强连通分量可以使其他所有的强连通分量都存在一条路径都连向它(因为有一个以上的强连通分量的出度为0,或没有出度为0的点),所以此时答案为0。 - 若该DAG不连通
则肯定不止有一个点的出度为0,所以答案为0。
综上:
其答案为两个部分
- 若有且仅有一个强连通分量的出度为0
答案为该强连通分量的大小 - 否则
答案为0
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int dfn[10010],low[10010],scc[10010];
int cnt,scnt;
bool vis[10010];
stack<int> s;
int h[10010],to[50010],nxt[50010],from[50010],tot;
int res[10010];
int out[10010];
void add(int x,int y)
{tot++;to[tot]=y;nxt[tot]=h[x];from[tot]=x;h[x]=tot;
}
void tj(int x)
{dfn[x]=low[x]=++cnt;s.push(x);vis[x]=1;for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x]){int y;scnt++;do{y=s.top();s.pop();vis[y]=0;scc[y]=scnt;res[scnt]++;}while(x!=y);}
}
int main()
{int n,m;cin>>n>>m;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;add(x,y);}for(int i=1;i<=n;i++){if(!dfn[i]){tj(i);}}for(int i=1;i<=tot;i++){int x=from[i];int y=to[i];if(scc[x]!=scc[y]){out[scc[x]]++;}}int ans=0;for(int i=1;i<=scnt;i++){if(out[i]==0&&ans==0){ans=res[i];}else if(out[i]==0){ans=0;}}cout<<ans;return 0;
}
0100
SP14887 GOODA - Good Travels
这题不知对不对,没有SPOJ账号交不了,且注册时弹不出验证码(和机房的网有关)
一句话题解
发现和模板题很想,即缩点+拓扑排序,然后规定一下起始点做拓扑即可。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int h1[1000010],nxt1[1000010],to1[1000010],tot1;
int h[1000010],from[1000010],to[1000010],nxt[1000010],tot;
int dfn[1000010],low[1000010],scc[1000010];
int v[1000010];
int dis[1000010];
int val[1000010];
int scnt,cnt;
int in[1000010];
bool vis[1000010];
int n,m,ss,tt;
stack<int> s;
void add(int x,int y)
{tot++;from[tot]=x;to[tot]=y;nxt[tot]=h[x];h[x]=tot;
}
void add1(int x,int y)
{tot1++;to1[tot1]=y;nxt1[tot1]=h1[x];h1[x]=tot1;
}
void tj(int x)
{dfn[x]=low[x]=++cnt;s.push(x);vis[x]=1;for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x]){int y;scnt++;do{y=s.top();s.pop();vis[y]=0;scc[y]=scnt;val[scnt]+=v[y];}while(x!=y);}
}
void topu()
{queue <int> q;q.push(ss);dis[ss]=val[ss];while(!q.empty()){int x=q.front();q.pop();for(int i=h1[x];i;i=nxt1[i]){int y=to1[i];dis[y]=max(dis[y],dis[x]+val[y]);in[y]--;if(in[y]==0){q.push(y);}}}
}
int main()
{cin>>n>>m>>ss>>tt;for(int i=1;i<=n;i++){cin>>v[i];}for(int i=1;i<=m;i++){int x,y;cin>>x>>y;add(x,y);}for(int i=1;i<=n;i++){if(!dfn[i]){tj(i);}}for(int i=1;i<=m;i++){int x=from[i];int y=to[i];if(scc[x]!=scc[y]){add1(scc[x],scc[y]);in[scc[y]]++;}}ss=scc[ss];tt=scc[tt];topu();cout<<dis[tt];return 0;
}
0101
洛谷 P1073 [NOIP 2009 提高组] 最优贸易
好像是个困难题?
一句话题解
这题还是太神秘了。
观察可得,该题的答案为在图上找到一条1->n的路径,选取一个最小值和最大值(最大值要在最小值的后面),然后做差。
发现,对于一个强连通分量来说,其不用考虑最大值和最小值的先后顺序,即直接从该强连通分量里取最大值和最小值即可,并分别记录成\(maxx[x],minn[x]\)
然后就可以理所应当的做一个缩点的操作。
考虑缩点后得到的DAG(有向无环图),在上面做一个拓扑排序求DP的操作。
考虑定义状态 \(dp[x]\) 表示对于从1到\(x\)这个强连通分量,此条路径的最小值是多少。
该点答案为此连通分量的最大值减去\(dp[x]\),此时一定可以保证最小值在最大值的后面。
DP转移方程为:$$dp[y]=min(dp[x],minn[y])$$
此时\(x\)与\(y\)直接连通。
最后答案为
但其实我们漏了一个东西,我们并没有考虑\(1->x\),\(x->n\)是否有路径。
那怎么办?
让我们来分着考虑下:
-
\(1->x\)
在Taijan上做文章,只对与1连通的点求强连通分量,然后在缩点建DAG图的时候特判:若其强连通分量标号为0,则跳过不建边 -
\(x->n\)
考虑缩点后建一个DAG图的反图,然后以\(n\)的强连通分量标号为起点,进行DFS,经过的点用一个bool数组(ycl)打标记即可。
然后答案统计变为:
tips:此题因为我的实现原因数组要开1e6,坏坏坏。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int h[1000100],to[1000100],from[1000100],nxt[1000100],tot;
int h1[1000100],to1[1000100],nxt1[1000100],tot1;
int h2[1000100],to2[1000100],nxt2[1000100],tot2;
int dfn[1000100],low[1000100],scc[1000100];
int minn[1000100],maxx[1000100];
int a[1000100],dp[1000100];
int cnt,scnt;
bool vis[1000100];
bool ycl[1000100];
int in[1000100];
stack <int> s;
void add(int x,int y)
{tot++;to[tot]=y;from[tot]=x;nxt[tot]=h[x];h[x]=tot;
}
void add1(int x,int y)
{tot1++;to1[tot1]=y;nxt1[tot1]=h1[x];h1[x]=tot1;
}
void add2(int x,int y)
{tot2++;to2[tot2]=y;nxt2[tot2]=h2[x];h2[x]=tot2;
}
void tj(int x)
{dfn[x]=low[x]=++cnt;s.push(x);vis[x]=1;for(int i=h[x];i;i=nxt[i]){int y=to[i];if(!dfn[y]){tj(y);low[x]=min(low[x],low[y]);}else if(vis[y]){low[x]=min(low[x],dfn[y]);}}if(low[x]==dfn[x]){int y;scnt++;do{y=s.top();s.pop();vis[y]=0;scc[y]=scnt;minn[scnt]=min(minn[scnt],a[y]);maxx[scnt]=max(maxx[scnt],a[y]);}while(x!=y);}
}
void dfs(int x)
{ycl[x]=1;for(int i=h2[x];i;i=nxt2[i]){int y=to2[i];if(ycl[y]){continue;}dfs(y);}
}
void topu()
{queue<int> q;q.push(scc[1]);dp[scc[1]]=minn[scc[1]];while(!q.empty()){int x=q.front();q.pop();for(int i=h1[x];i;i=nxt1[i]){int y=to1[i];dp[y]=min(dp[x],minn[y]);in[y]--;if(in[y]==0){q.push(y);}}}int ans=0;for(int i=1;i<=scnt;i++){if(ycl[i]){ans=max(ans,maxx[i]-dp[i]);}}cout<<ans;
}
int main()
{memset(dp,0x3f,sizeof(dp));memset(minn,0x3f,sizeof(minn));int n,m;cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=m;i++){int x,y,z;cin>>x>>y>>z;if(z==1){add(x,y);}if(z==2){add(x,y);add(y,x);}}tj(1);for(int i=1;i<=tot;i++){int x=from[i];int y=to[i];if(scc[x]==0||scc[y]==0||scc[x]==scc[y]){continue;}in[scc[y]]++;add1(scc[x],scc[y]);add2(scc[y],scc[x]);}dfs(scc[n]);topu();return 0;
}
0110
洛谷 P2272 [ZJOI2007] 最大半连通子图