这篇博客记录一下Tarjan缩点的模板题。
要是对Tarjan和缩点不了解的可以去看我上一篇博客
首先是洛谷的
P3387 【模板】缩点
题意很明显,就是求一条路径使经过的权值之和最大。
那么可以通过缩点+拓扑排序来解决问题。
注意的问题:
1.缩点时要把强连通分量的点的权值加到最后缩成点的那个权值中。
2.拓扑排序:简单来说就是在DAG图中寻找一个适合的解决问题的顺序,而这里这个顺序就是这个题目要求找的路径。
题解如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
typedef pair<int, int> P;
const int maxn = 1e5+5;
const ll mod = 1e9+7;
#define IO ios::sync_with_stdio(0)
using namespace std;
vector<int> g[maxn];
int val[maxn],low[maxn], dfn[maxn], sta[maxn], ins[maxn], belong[maxn];
int cnt, ind, tot; //cnt:强连通分量的数量, ind:时间戳, tot:sta的top
int sd[maxn];//缩点点后在哪个点
int in[maxn];//入度
int dist[maxn];
vector<int> sdg[maxn];
int n,m;
void init()
{
memset(ins, 0, sizeof(ins));
memset(belong, 0, sizeof(belong));
memset(dfn, 0, sizeof(dfn));
cnt = ind = tot = 0;
}
//求强连通分量
void Tarjan(int u)
{
low[u] = dfn[u] = ++ind;
ins[u] =1;
sta[++tot] = u;
for(int i=0;i<g[u].size();i++)
{
int v = g[u][i];
if(!dfn[v])
{
Tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(ins[v])
{
low[u] = min(low[u],low[v]);
}
}
int p;
if(low[u] == dfn[u])
{
++cnt;
int y;
while(y=sta[tot--])
{
sd[y] = u;
ins[y] = 0;
if(u==y) break;
val[u]+=val[y];
//cout<<"asd"<<endl;
}
//这行代码会加了两次
// do
// {
// p = sta[tot--];
// sd[p] =u;
// val[u]+=val[p];
// cout<<"asd"<<endl;
// //belong[p] = cnt;
// ins[p] = 0;
// }while(p!=u);
}
}
int tuopu()
{
queue <int> q;
//int tot=0;
for (int i=1;i<=n;i++)
if (sd[i]==i&&!in[i])//入度为0,且是最后的那个缩点
{
q.push(i);
dist[i]=val[i];
//cout<<i<<" "<<dist[i]<<endl;
}
while(!q.empty())
{
int k=q.front();q.pop();
for(int i=0;i<sdg[k].size();i++)
{
int v = sdg[k][i];
dist[v] = max(dist[v],dist[k]+val[v]);
in[v]--;
if(in[v]==0) q.push(v);
}
}
int ans=0;
for (int i=1;i<=n;i++)
ans=max(ans,dist[i]);
return ans;
}
int main()
{
IO;
cin>>n>>m;
for (int i=1;i<=n;i++)
cin>>val[i];
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
g[a].push_back(b);
}
init();
for (int i=1;i<=n;i++)
if (!dfn[i]) Tarjan(i);
// for(int i=1;i<=n;i++)
// cout<<belong[i]<<" ";
// for(int i=1;i<=n;i++)
// cout<<sd[i]<<" ";
//缩点,建图
for(int i=1;i<=n;i++)
for(int j=0;j<g[i].size();j++)
{
int x = sd[i],y=sd[g[i][j]];
//cout<<x<<" "<<y<<endl;
if(x!=y)
{
sdg[x].push_back(y);
in[y]++;
}
}
cout<<tuopu()<<endl;
return 0;
}
接下里也是洛谷的tarjan题受欢迎的牛
由题可得,受欢迎的奶牛只有可能是图中唯一的出度为零的强连通分量中的所有奶牛,所以若出现两个以上出度为0的强连通分量则不存在明星奶牛,因为那几个出度为零的分量的爱慕无法传递出去。那唯一的分量能受到其他分量的爱慕同时在分量内相互传递,所以该分量中的所有奶牛都是明星。
但其实这里不用缩点,tarjan时标记处理下就好。不过我这里懒得改,缩了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
typedef pair<int, int> P;
const int maxn = 1e5+5;
const ll mod = 1e9+7;
#define IO ios::sync_with_stdio(0)
using namespace std;
vector<int> g[maxn];
int low[maxn], dfn[maxn], sta[maxn], ins[maxn], belong[maxn];
int cnt, ind, tot; //cnt:强连通分量的数量, ind:时间戳, tot:sta的top
int sd[maxn];//缩点点后在哪个点
int chu[maxn];//入度
int all[maxn];
vector<int> sdg[maxn];
int n,m;
void init()
{
memset(ins, 0, sizeof(ins));
memset(belong, 0, sizeof(belong));
memset(dfn, 0, sizeof(dfn));
cnt = ind = tot = 0;
}
//求强连通分量
void Tarjan(int u)
{
low[u] = dfn[u] = ++ind;
ins[u] =1;
sta[++tot] = u;
for(int i=0;i<g[u].size();i++)
{
int v = g[u][i];
if(!dfn[v])
{
Tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(ins[v])
{
low[u] = min(low[u],low[v]);
}
}
int p;
if(low[u] == dfn[u])
{
++cnt;
int y;
while(y=sta[tot--])
{
sd[y] = cnt;
ins[y] = 0;
all[cnt]++;//将一个分量染成一个颜色
if(u==y) break;
//cout<<"asd"<<endl;
}
//这行代码会加了两次
// do
// {
// y = sta[tot--];
// sd[y] = cnt;
// all[cnt]++;
// ins[y] = 0;
//
// }while(y!=u);
}
}
int main()
{
IO;
cin>>n>>m;
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
g[a].push_back(b);
}
init();
for (int i=1;i<=n;i++)
if (!dfn[i]) Tarjan(i);
// for(int i=1;i<=n;i++)
// cout<<belong[i]<<" ";
// for(int i=1;i<=n;i++)
// cout<<sd[i]<<" ";
//缩点,并记录缩点后的入度
for(int i=1;i<=n;i++)
for(int j=0;j<g[i].size();j++)
{
int x = sd[i],y=sd[g[i][j]];
//cout<<x<<" "<<y<<endl;
if(x!=y)
{
sdg[x].push_back(y);
chu[x]++;
}
}
int ansindex = 0;
for(int i=1;i<=cnt;i++)
{
if(!chu[i])
{
if(ansindex)
{
cout<<0<<endl;
return 0;
}
ansindex = i;
}
}
//cout<<ansindex<<endl;
cout<<all[ansindex]<<endl;
return 0;
}
然是消息扩散
这道题目和上面那道受欢迎的牛很像,先缩一下点,然后统计一下入度为0的点即可 。
这道的题解就不用放啦。
间谍网络
题意就看题吧。
解题思路:
这题有两种情况:
一是有的罪犯既不能贿赂他也没有罪犯能揭发他,那么此题无解,我们在遍历时打上标记,然后从小到大枚举,只要遇见没有标记的就输出NO和对应的点然后退出即可。(注意,我们是要从能被贿赂的罪犯作为起点Tarjan(dfs),代码有注释)
二是所有的罪犯都能直接或间接地被能贿赂的罪犯揭发。然后这里,也有两种情况,一是没有环,那么资金就是贿赂所有入度为0的罪犯,二是有环,那么资金就是那个环里罪犯所需资金最小的。我们想,如果我们把环里的罪犯缩成一个点,那么全都是前者的情况了,也是贿赂所有入度为0的点。
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
typedef pair<int, int> P;
const int maxn = 1e5+5;
const ll mod = 1e9+7;
#define IO ios::sync_with_stdio(0)
using namespace std;
vector<int> g[maxn];
int low[maxn], dfn[maxn], sta[maxn], ins[maxn], belong[maxn];
int cnt, ind, tot; //cnt:强连通分量的数量, ind:时间戳, tot:sta的top
int sd[maxn];//缩点点后在哪个点
int in[maxn];//入度
int sum[maxn];
int money[maxn];
vector<int> sdg[maxn];
int n,m;
void init()
{
memset(ins, 0, sizeof(ins));
memset(belong, 0, sizeof(belong));
memset(dfn, 0, sizeof(dfn));
cnt = ind = tot = 0;
}
//求强连通分量
void Tarjan(int u)
{
low[u] = dfn[u] = ++ind;
ins[u] =1;
sta[++tot] = u;
for(int i=0;i<g[u].size();i++)
{
int v = g[u][i];
if(!dfn[v])
{
Tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(ins[v])
{
low[u] = min(low[u],low[v]);
}
}
int p;
if(low[u] == dfn[u])
{
++cnt;
int y;
while(y=sta[tot--])
{
sd[y] = cnt;//缩点,从1下标开始
ins[y] = 0;
sum[cnt] = min(sum[cnt],money[y]);
if(u==y) break;
//cout<<"asd"<<endl;
}
//这行代码会加了两次
// do
// {
// y = sta[tot--];
// sd[y] = cnt;
// all[cnt]++;
// ins[y] = 0;
//
// }while(y!=u);
}
}
int main()
{
IO;
cin>>n;
for(int i=1;i<=n;i++)
money[i]=inf,sum[i] = inf;
int q;
cin>>q;
for(int i=1;i<=q;i++)
{
int a,b;
cin>>a>>b;
money[a] = b;
}
cin>>m;
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
g[a].push_back(b);
}
init();
//如果他能够被贿赂就以他为起点找环
for (int i=1;i<=n;i++)
if (!dfn[i]&&money[i]!=inf) Tarjan(i);
for(int i=1;i<=n;i++)//在这里我们用dfn数组来判断它是否被遍历过
if(!dfn[i])
{
cout<<"NO\n";
cout<<i<<endl;
return 0;
}
//缩点,并记录缩点后的入度,这里可以不用再构图了
for(int i=1;i<=n;i++)
for(int j=0;j<g[i].size();j++)
{
int x = sd[i],y=sd[g[i][j]];
//cout<<x<<" "<<y<<endl;
if(x!=y)
{
sdg[x].push_back(y);
in[y]++;
}
}
int ans = 0;
for(int i=1;i<=cnt;i++)
{
if(!in[i])//入度为0
{
ans+=sum[i];
}
}
cout<<"YES\n";
cout<<ans<<endl;
return 0;
}