Tarjan 求图点强联通,桥的应用
在图中求双联通和强联通分量是我们解决非树结构的图连通问题的利器
通过求求图的双联通和强联通分量能把图转化成DAG进行求解;
行走
Description
给出一个有向图,你可以选择从任意点出发走到任意点结束,问最多可以经过多少个点(重复经过只算一次)。
Input Format
第一行,两个整数,n和m。表示有向图的点数和边数。
接下来是m行每行输入两个数a,b,表示有一条从a到b的路。
Output Format
输出最多可以经过的点数
Sample Input
10 10 6 4 0 8 5 9 8 1 7 7 4 8 5 9 7 5 9 5 7 7
Sample Output
4
Hint
对于30%的数据,1≤n,m≤10
对于100%的数据,1≤n,m≤10000,0≤a,b
题目不保证图是拓扑结构(DAG)那么就有可能出现自环枚举每个点搜图效率慢T(n(m+n));
暴力算法明显超时 这时求强联通分量进行缩点就派上用场;
若把图转成DAG那么就可以通过每颗树的根节点搜索图 进行统计了T(n+m)
#include<cstdio> #include<cstring> #include<iostream> #include<cmath> #define maxn 100010 using namespace std; int n,m,i,j,k,top,tim,t,co,ans; struct st{int nex,to;}mu[2*maxn]; int s[maxn],pre[maxn],low[maxn],last[maxn],bcc[maxn],xx[maxn],yy[maxn],sum[maxn],bo[maxn]; int dfs(int u) { int v; s[++top]=u; low[u]=pre[u]=++tim; for(int z=last[u];z;z=mu[z].nex) { v=mu[z].to; if(!pre[v]) { dfs(v); low[u]=min(low[u],low[v]); } else if(!bo[v]) low[u]=min(low[u],pre[v]); } if(low[u]==pre[u]) { ++co; int num=0; for(; ;) { num++; bo[s[top]]=co; bcc[s[top--]]=co; if(s[top+1]==u)break; } sum[co]=num; } return low[u]; } void dfs1(int u) { low[u]=sum[u]; int p=0; for(int z=last[u];z;z=mu[z].nex) { if(!low[mu[z].to]) dfs1(mu[z].to); p=max(p,low[mu[z].to]); } low[u]+=p; } int main() { // freopen("xx.in","r",stdin); scanf("%d%d",&n,&m); for(i=1;i<=m;++i) { int u,v; scanf("%d%d",&xx[i],&yy[i]); t++;mu[t].nex=last[xx[i]]; mu[t].to=yy[i];last[xx[i]]=t; } for(i=0;i<n;++i) if(!pre[i]) { tim=0;top=0;dfs(i); } for(i=0;i<=co;++i) low[i]=s[i]=last[i]=0;t=0; for(i=1;i<=m;++i) { int u=bcc[xx[i]],v=bcc[yy[i]]; if(u==v)continue; t++;mu[t].nex=last[u]; mu[t].to=v;last[u]=t;s[v]++; } for(i=1;i<=co;++i) if(!s[i]) { dfs1(i); ans=max(ans,low[i]); } printf("%d",ans); }
间谍网络
Description
由于外国间谍的大量渗入,国家安全正处于高度的危机之中。如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B。有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报。所以,如果我们能够收买一些间谍的话,我们就可能控制间谍网中的每一分子。因为一旦我们逮捕了一个间谍,他手中掌握的情报都将归我们所有,这样就有可能逮捕新的间谍,掌握新的情报。
我们的反间谍机关提供了一份资料,色括所有已知的受贿的间谍,以及他们愿意收受的具体数额。同时我们还知道哪些间谍手中具体掌握了哪些间谍的资料。假设总共有n个间谍(n不超过3000),每个间谍分别用1到3000的整数来标识。
请根据这份资料,判断我们是否有可能控制全部的间谍,如果可以,求出我们所需要支付的最少资金。否则,输出不能被控制的一个间谍。
Input Format
输入文件age.in第一行只有一个整数n。
第二行是整数p。表示愿意被收买的人数,1≤p≤n。
接下来的p行,每行有两个整数,第一个数是一个愿意被收买的间谍的编号,第二个数表示他将会被收买的数额。这个数额不超过20000。
紧跟着一行只有一个整数r,1≤r≤8000。然后r行,每行两个正整数,表示数对(A, B),A间谍掌握B间谍的证据。
Output Format
如果可以控制所有间谍,第一行输出YES,并在第二行输出所需要支付的贿金最小值。否则输出NO,并在第二行输出不能控制的间谍中,编号最小的间谍编号。
Sample Input
【样例1】 age.in 3 2 1 10 2 100 2 1 3 2 3 【样例2】 age.in 4 2 1 100 4 200 2 1 2 3 4
Sample Output
【样例1】 YES 110 【样例2】 NO 3
如果间谍网出现强联通分量那么只要其中一个能被收买就能逮捕强联通分量内的所有间谍(只收买最便宜的间谍);
那么就求强联通进行缩点 ;然后只要收买树根的间谍就行了;
为什么? 因为你要收买所有间谍 树根的间谍一定要收买 那么一整棵树的间谍都被逮捕了;
只要每颗树的树根都能收买 那么一定能控制所有间谍;
#include<cstdio> #include<iostream> #include<cmath> #include<algorithm> #define maxn 3010 using namespace std; int n,i,j,k,l,m,p,co,tim,top,t,ans; struct st{int nex,to;}mu[maxn*6]; int s[maxn],last[maxn],bcc[maxn],bo[maxn],pre[maxn],low[maxn],b[maxn],xx[maxn*3],yy[maxn*3]; int dfs(int u) { pre[u]=low[u]=++tim; int v;s[++top]=u; for(int z=last[u];z;z=mu[z].nex) { v=mu[z].to; if(!pre[v]) { dfs(v); low[u]=min(low[u],low[v]); } else if(!b[maxn]) low[u]=min(low[u],pre[v]); } if(pre[u]==low[u]) { ++co; for(; ;) { b[s[top]]=1; bcc[s[top--]]=co; if(s[top+1]==u)break; } } } void add(int x,int y) { t++;mu[t].nex=last[x]; mu[t].to=y;last[x]=t; } int main() { // freopen("xx.in","r",stdin); scanf("%d%d",&n,&p); for(i=1;i<=p;++i) { int u,v; scanf("%d%d",&u,&v); bo[u]=v+1; } scanf("%d",&m); for(i=1;i<=m;++i) { int u,v; scanf("%d%d",&u,&v); add(u,v); xx[i]=u;yy[i]=v; } for(i=1;i<=n;++i) if(!pre[i]) { top=tim=0; dfs(i); } for(i=1;i<=co;low[i]=1e9,++i)s[i]=b[i]=0; for(i=1;i<=m;++i) { if(bcc[xx[i]]==bcc[yy[i]])continue; s[bcc[yy[i]]]=1; } for(i=1;i<=n;++i) if(bo[i]) low[bcc[i]]=min(low[bcc[i]],bo[i]-1); for(i=1;i<=co;++i) if(!s[i]) { if(low[i]==1e9) { for(j=1;j<=n;++j) if(bcc[j]==i)break; printf("NO\n%d",j); return 0; } ans+=low[i]; } printf("YES\n%d",ans); }
Freda的迷宫
Description
Freda是一个迷宫爱好者,她利用业余时间建造了许多迷宫。每个迷宫都是由若干房间和走廊构成的,每条走廊都连接着两个不同的房间,两个房间之间最多只有一条走廊直接相连,走廊都是双向通过。
黄昏时候,Freda喜欢在迷宫当中漫步。每天,Resodo都会为Freda设计一个挑战方案。Resodo会指定起点和终点,请Freda来找到一条从起点到终点的简单路径。一条简单路径定义为一个房间序列,每个房间至多在序列里出现一次,且序列中相邻的两个房间有走廊相连。当起点和终点之间存在且仅存在一条简单路径的时候,Freda认为这个挑战方案是RD的。现在,请你帮帮Resodo来写一个程序,判断一个挑战方案是否是RD的。
Input Format
第一行三个整数N,M,Q.分别表示房间数,走廊数,询问数。
接下来M行每行2个整数x,y, 0<=N, 表示x和y之间有一条走廊相连。
接下来Q行每行2个整数x,y, 表示询问以x为起点,y为终点的挑战方案是否是RD的.
Output Format
对于每个询问,输出一行”Y”或者”N”(不含引号).Y表示该询问所表示的挑战方案是RD的,N表示该询问所表示的挑战方案不是RD的.
Sample Input
6 5 3 1 2 2 3 2 4 2 5 4 5 1 3 1 5 2 6
Sample Output
Y N N
Hint
样例解释
1,3之间只有一条路径 1->2->3
1,5之间有两条路径 1->2->5 ; 1->2->4->5
1,6之间没有路径
数据范围与约定
对于30%的数据,N<=100, M<=1000, Q<=100.
对于50%的数据,N<=1000, M<=10000, Q<=1000.
对于100%的数据,N<=10000, M<=100000, Q<=10000.
按题意在线处理定会超时;
怎么做到离线算法?
首先有简单路径必须保证两点联通 且只有唯一路径;
那么这条路径一定由桥组成;那就可以保证路径唯一,且联通;
用Tarjan求桥;
将桥两端点归进一个集合 这时就要用到并查集了;
最后对于每个询问只要判断两点在同一集合就能保证有唯一路径;
#include<cstdio> #include<iostream> #define maxn 10010 using namespace std; int n,t,i,j,k,l,m,q,tim,_bcc,_scc,top,fu,fv; int last[maxn],bcc[maxn],scc[maxn],pre[maxn],low[maxn],f[maxn]; struct st{int nex,to;}mu[maxn*20]; void add(int x,int y) { t++;mu[t].nex=last[x]; mu[t].to=y;last[x]=t; } int gets(int x) {return f[x]==x ? x:f[x]=gets(f[x]);} int dfs(int u,int fa) { pre[u]=low[u]=++tim; int v; for(int z=last[u];z;z=mu[z].nex) { v=mu[z].to; if(!pre[v]) { dfs(v,u); low[u]=min(low[u],low[v]); if(pre[u]<low[v]) { fu=gets(u);fv=gets(v); if(fu!=fv)f[fv]=fu; } } else if(pre[u]>pre[v]&v!=fa) low[u]=min(pre[v],low[u]); } return low[u]; } int main() { // freopen("xx.in","r",stdin); scanf("%d%d%d",&n,&m,&q); for(i=1;i<=m;++i) { int u,v; scanf("%d%d",&u,&v); add(u,v);add(v,u); } for(i=1;i<=n;++i)f[i]=i; for(i=1;i<=n;++i) if(!pre[i]) { tim=0;top=0; dfs(i,-1); } for(i=1;i<=q;++i) { int u,v; scanf("%d%d",&u,&v); u=gets(u);v=gets(v); if(u==v)printf("Y\n"); else printf("N\n"); } }