Tarjan 算法做题记录
T1-受欢迎的牛
Solution
裸题,Tarjan 缩点,找到那一个出度为 \(0\) 的点,把它所在的强连通分量的大小输出。如果有多个就无解。
Code
这题是贺我们老师的,所以码风不像我的。
#include<bits/stdc++.h>
using namespace std;
#define maxn 11000
int t,n,m,size,du[maxn],line[maxn];
vector<int>edge[maxn];
vector<int>E[maxn];
int low[maxn],dfn[maxn] ,s[maxn],dfncnt,tp,sc,scc[maxn],sz[maxn];
bool in_stack[maxn];
void tarjan(int u) {
low[u] = dfn[u] = ++dfncnt, s[++tp] = u, in_stack[u] = 1;
for (int v:edge[u]) {
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stack[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
++sc;
do{
scc[s[tp]] = sc;
sz[sc]++;
in_stack[s[tp]] = 0;
--tp;
}while (s[tp+1] != u);
//cout<<sz[sc]<<" "<<u<<endl;
}
}
void work(){
for(int i=1;i<=n;i++)if(!dfn[i])
tarjan(i);
for(int i=1;i<=n;i++)
for(int y:edge[i])if(scc[i]!=scc[y]){
E[scc[i]].push_back(scc[y]);
du[scc[i]]++;
// cout<<du[scc[y]]<<" scc="<<scc[y]<<endl;
}
}
int main(){
cin>>n>>m;
for(int i=1,a,b;i<=m;i++){
cin>>a>>b;
edge[a].push_back(b);
}
work();
int kk=0,t;
for(int i=1;i<=sc;i++)
if(du[i]==0)kk++,t=i;
if(kk==1){
cout<<sz[t]<<endl;
}else cout<<0<<endl;
}
T2-最大半连通子图
Solution
这个半连通子图和强连通分量的定义很像,可以发现,强连通分量必定是半连通子图,所以我们先 Tarjan 缩点,变成一个 DAG。然后再想,两个强连通分量通过一条有向边相连,必定是一个半连通子图。所以,问题就变成了再缩完点的 DAG 上寻找最长链。
然后进行拓扑排序,边排序边 DP,\(f(i)\) 表示到节点 \(i\) 的最长链大小,\(g(i)\) 表示到节点 \(i\) 最长链个数,要记得判重边。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
void read(int &x)
{
char ch=getchar();
int r=0,w=1;
while(!isdigit(ch))w=ch=='-'?-1:1,ch=getchar();
while(isdigit(ch))r=(r<<3)+(r<<1)+(ch^48),ch=getchar();
x=r*w;
}
const int N=2e5+7;
vector<int>edge[N];
vector<int>E[N];
int n,m,mod;
int dfncnt,low[N],dfn[N],stk[N],scc[N],sc,top,sz[N],du[N];
bool in_stack[N];
map<pair<int,int>,bool>mp;
queue<int>q;
int f[N],g[N];
void tarjan(int u)
{
low[u]=dfn[u]=++dfncnt;stk[++top]=u;in_stack[u]=1;
for(int v:edge[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in_stack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
++sc;
do
{
scc[stk[top]]=sc;
sz[sc]++;
in_stack[stk[top]]=0;
--top;
}while(stk[top+1]!=u);
}
}
void work()
{
for(int u=1;u<=n;u++)
for(int v:edge[u])
if(scc[u]!=scc[v]&&!mp[make_pair(scc[u],scc[v])])
E[scc[u]].push_back(scc[v]),mp[make_pair(scc[u],scc[v])]=1,du[scc[v]]++;
}
void topsort()
{
for(int i=1;i<=sc;i++)
if(!du[i])
{
q.push(i);
f[i]=sz[i];
g[i]=1;
}
while(!q.empty())
{
int u=q.front();q.pop();
for(int v:E[u])
{
du[v]--;
if(!du[v])q.push(v);
if(f[u]+sz[v]==f[v])
{
g[v]+=g[u];
g[v]%=mod;
}
else if(f[u]+sz[v]>f[v])
{
g[v]=g[u];
f[v]=f[u]+sz[v];
}
}
}
int ans=0,ans1=0;
for(int i=1;i<=sc;i++)
{
if(f[ans]<f[i])
{
ans=i;
ans1=g[i];
}
else if(f[ans]==f[i])
ans1+=g[i];
ans1%=mod;
}
printf("%lld\n%lld",f[ans],ans1);
}
main()
{
read(n);read(m);read(mod);
for(int i=1,x,y;i<=m;i++)
read(x),read(y),edge[x].push_back(y);
for(int i=1;i<=n;i++)
if(!dfn[i])tarjan(i);
work();topsort();
return 0;
}
T3-网络协议
Solution
第一问很好解决,缩点后寻找入度为 \(0\) 的点即可。
对于第二问,要求加边,使缩点后的每个点都能到另一个点,即每个点都需要有入度和出度都大于 \(1\),即我们寻找入度为 \(0\) 的点和出度为 \(0\) 的点,取最大值即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
void read(int &x)
{
char ch=getchar();
int r=0,mp=1;
while(!isdigit(ch))mp=ch=='-'?-1:1,ch=getchar();
while(isdigit(ch))r=(r<<3)+(r<<1)+(ch^48),ch=getchar();
x=r*mp;
}
const int N=1e4+7;
int n,m,k,dfncnt,size,top,scc;
int dfn[N],low[N],stk[N],sc[N],sz[N],du[N],f[N];
bool in_stack[N];
map<pair<int,int>,bool>mp;
vector<int>edge[N];
vector<int>Edge[N];
queue<int>q;
void tarjan(int u)
{
dfn[u]=low[u]=++dfncnt;
stk[++top]=u;
in_stack[u]=1;
for(int v:edge[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
{
if(in_stack[v])
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u])
{
scc++;
do
{
sc[stk[top]]=scc;
sz[scc]++;
in_stack[stk[top]]=0;
top--;
}
while(stk[top+1]!=u);
}
}
main()
{
read(n);
for(int i=1;i<=n;i++)
{
int p;
read(p);
while(p!=0)
{
edge[i].push_back(p);
read(p);
}
}
for(int i=1;i<=n;i++)
if(!dfn[i])tarjan(i);
for(int u=1;u<=n;u++)
{
for(int v:edge[u])
if(sc[u]!=sc[v]&&mp[make_pair(sc[u],sc[v])]!=1)
{
Edge[sc[u]].push_back(sc[v]);
mp[make_pair(sc[u],sc[v])]=1;
du[sc[v]]++;
f[sc[u]]++;
}
}
int ans=0,anss=0;
for(int i=1;i<=scc;i++)
{
if(!du[i])ans++;
if(!f[i])anss++;
}
printf("%lld",ans);
if(scc==1)printf("\n%lld",0);
else printf("\n%lld",max(ans,anss));
return 0;
}
T4-消息的传递
Solution
跟上一题一样的,还不要第二问。
T5-间谍网络
Solution
这一题跟前几题不同,跑 Tarjan 的条件要多一个可以被收买,因为在一个强连通分量中必须要有一个可以被收买的才能够全部逮捕。
缩点的时候记录一下最少的收买值。统计答案时如果有一个点的时间戳没有被更新过,那就代表他既不能被收买,也不能被别人告发,那就是不可控制。
找到所有入度为 \(0\) 的点,加上它的收买值就好了。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
void read(int &x)
{
char ch=getchar();
int r=0,w=1;
while(!isdigit(ch))w=ch=='-'?-1:1,ch=getchar();
while(isdigit(ch))r=(r<<3)+(r<<1)+(ch^48),ch=getchar();
x=r*w;
}
const int N=2e5+7;
vector<int>edge[N];
vector<int>E[N];
int n,m,mod,a[N],sum[N];
int dfncnt,low[N],dfn[N],stk[N],scc[N],sc,top,sz[N],du[N];
bool in_stack[N];
map<pair<int,int>,bool>mp;
queue<int>q;
int f[N],g[N];
void tarjan(int u)
{
low[u]=dfn[u]=++dfncnt;stk[++top]=u;in_stack[u]=1;
for(int v:edge[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in_stack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
++sc;
do
{
scc[stk[top]]=sc;
sz[sc]++;
in_stack[stk[top]]=0;
sum[sc]=min(sum[sc],a[stk[top]]);
--top;
}while(stk[top+1]!=u);
}
}
void work()
{
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
printf("NO\n");
printf("%d\n",i);
return;
}
}
for(int u=1;u<=n;u++)
for(int v:edge[u])
if(scc[u]!=scc[v]&&!mp[make_pair(scc[u],scc[v])])
E[scc[u]].push_back(scc[v]),mp[make_pair(scc[u],scc[v])]=1,du[scc[v]]++;
printf("YES\n");
int ans=0;
for(int i=1;i<=sc;i++)
if(!du[i])ans+=sum[i];
printf("%d",ans);
}
main()
{
int p;
read(n);read(p);//read(m);
memset(a,63,sizeof a);
memset(sum,63,sizeof sum);
for(int i=1,x;i<=p;i++)
read(x),read(a[x]);
read(m);
for(int i=1,x,y;i<=m;i++)
read(x),read(y),edge[x].push_back(y);
for(int i=1;i<=n;i++)
if(!dfn[i]&&a[i]<1e9)tarjan(i);
work();
return 0;
}
T6-抢掠计划
Solution
既然可以重复走,那我们先缩点,每个点的点权定义为这个强连通分量里面 ATM 的和,然后把点权移到边权,跑最长路,最后再枚举每一个酒吧,取最长路的最大值。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
void read(int &x)
{
char ch=getchar();
int r=0,mp=1;
while(!isdigit(ch))mp=ch=='-'?-1:1,ch=getchar();
while(isdigit(ch))r=(r<<3)+(r<<1)+(ch^48),ch=getchar();
x=r*mp;
}
const int N=1e6+7;
int n,m,k,dfncnt,size,top,scc;
int dfn[N],low[N],stk[N],sc[N],sum[N],du[N],f[N],my[N],sb[N];
int d[N],pre[N],dis[N],son[N],now[N],tot;
bool in_stack[N],bb[N];
map<pair<int,int>,bool>mp;
vector<int>edge[N];
void add(int x,int y,int z)
{
pre[++tot]=now[x];
son[tot]=y;
dis[tot]=z;
now[x]=tot;
}
queue<int>q;
void tarjan(int u)
{
dfn[u]=low[u]=++dfncnt;
stk[++top]=u;
in_stack[u]=1;
for(int v:edge[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
{
if(in_stack[v])
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u])
{
scc++;
do
{
sc[stk[top]]=scc;
sum[scc]+=my[stk[top]];
in_stack[stk[top]]=0;
top--;
}
while(stk[top+1]!=u);
}
}
void spfa(int s)
{
queue<int>q;
memset(d,128,sizeof d);
memset(bb,false,sizeof bb);
d[s]=sum[s];q.push(s);bb[s]=1;
while(!q.empty())
{
int u=q.front();q.pop();
bb[u]=0;
for(int i=now[u];i;i=pre[i])
{
int v=son[i],w=dis[i];
if(d[v]<d[u]+w)
{
d[v]=d[u]+w;
if(!bb[v])q.push(v),bb[v]=1;
}
}
}
}
main()
{
read(n);read(m);
for(int i=1,x,y;i<=m;i++)
{
read(x);read(y);
edge[x].push_back(y);
}
for(int i=1;i<=n;i++)
read(my[i]);
for(int i=1;i<=n;i++)
if(!dfn[i])tarjan(i);
int s,p;
read(s);read(p);
for(int i=1;i<=p;i++)
read(sb[i]);
for(int u=1;u<=n;u++)
{
for(int v:edge[u])
if(sc[u]!=sc[v]&&mp[make_pair(sc[u],sc[v])]!=1)
{
add(sc[u],sc[v],sum[sc[v]]);
mp[make_pair(sc[u],sc[v])]=1;
}
}
spfa(sc[s]);
int ans=0;
for(int i=1;i<=p;i++)
ans=max(ans,d[sc[sb[i]]]);
cout<<ans;
return 0;
}