tarjan+缩点
总述:tarjan缩点可将复杂图变为DAG,后续可配合topo + dp 或者 记忆化搜索(推荐,其实与前面差不多,但更好写),在稠密图中效果更佳,可代替有向基环森林算法
1158. 受欢迎的牛(tarjan板子)
#include <bits/stdc++.h>
using namespace std;
int n,m;
int scc;
int iscc[10001];
int sccsz[10001];
vector <int> e1[10001];
vector <int> e2[10001];
int in1[10001],out1[10001],in2[10001],out2[10001];
int dfn[10001],low[10001];
int timer;
stack <int> STK;
int cnt,ans;
void tarjan(int x)
{
dfn[x] = low[x] = ++timer;
STK.push(x);
for (int i = 0; i < e1[x].size(); i++)
{
if(!dfn[e1[x][i]])//未访问过
{
tarjan(e1[x][i]);
low[x] = min(low[x],low[e1[x][i]]);
}
else
{
low[x] = min(low[x],dfn[e1[x][i]]);
}
}
if(dfn[x] == low[x])
{
scc++;
int tmp;
do{
tmp = STK.top();
STK.pop();
iscc[tmp] = scc;
sccsz[scc]++;
}while(tmp != x);
}
}
int main()
{
cin >> n >> m;
int a,b;
for (int i = 1; i <= m; i++)
{
cin >> a >> b;
e1[a].push_back(b);
in1[b]++,out1[a]++;
}
for (int i = 1; i <= n; i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < e1[i].size(); j++)
{
if(iscc[i] == iscc[e1[i][j]])continue;
in2[iscc[e1[i][j]]]++;
out2[iscc[i]]++;
}
}
for (int i = 1; i <= scc; i++)
{
if(out2[i] == 0)
{
cnt++;
ans = sccsz[i];
}
}
if(cnt > 1)cout << 0;
else cout << ans;
return 0;
}
此处需注意tarjan内部的应加上是否在stack内的判断,这样可以确保u可以到v
1159. 连通数
#include <bits/stdc++.h>
using namespace std;
int n;
char a;
vector <int>e1[2001],e2[2001];
int in1[2001],out1[2001],in2[2001],out2[2001],sz[2001];
int scc,iscc[2001],timer;
int dfn[2001],low[2001];
stack <int> STK;
int pas,vis[2001],ans;
bool inSTK[2001];
bitset<2010> f[2010];
void tarjan(int x)
{
dfn[x] = low[x] = ++timer;
STK.push(x);
inSTK[x] = true;
for (int i = 0; i < e1[x].size(); i++)
{
int u = e1[x][i];
if(!dfn[u])
{
tarjan(u);
low[x] = min(low[x],low[u]);
}
else if(inSTK[u])//一定要加是否在栈中,这保证了u可以到v
{
low[x] = min(low[x],dfn[u]);//只走一次非数边
}
}
if(dfn[x] == low[x])
{
int tmp;
scc++;
//cout << scc << ":";
do{
tmp = STK.top();
//cout << tmp << " ";
STK.pop();
iscc[tmp] = scc;
sz[scc]++;
inSTK[tmp] = false;
}while(tmp != x);
}
}
void F(int x)
{
for (int i = 0; i < e2[x].size(); i++)
{
int u = e2[x][i];
if(!vis[u])
{
vis[u] = 1;
F(u);
}f[x] |= f[u];
}
//cout << x << " ";
f[x][x] = 1;
//cout << x << " " << f[x] << endl;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
cin >> a;
if(a == '1')
{
e1[i].push_back(j);
in1[j]++,out1[i]++;
//f[i][j] = 1;
}
}
}
for (int i = 1; i <= n; i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < e1[i].size(); j++)
{
int u = e1[i][j];
if(iscc[u] == iscc[i])continue;
e2[iscc[i]].push_back(iscc[u]);
in2[iscc[u]]++,out2[iscc[i]]++;
}
}
for (int i = 1; i <= scc; i++)
{
if(in2[i] || vis[i])continue;//找一个入度为0的d点
vis[i] = 1;
F(i);
//cout << pas << " ";
}
for (int i = 1; i <= scc; i++)
{
for (int j = 1; j <= scc; j++)
{
if(f[i][j] == 1)ans += sz[i] * sz[j];
}
}
cout << ans;
return 0;
}
缩点后,重建图,再记忆化搜索一遍
一开始F函数写错了,f[x] |= f[u]是要放在if判断外面的,不然就更新不了了;还有就是这里不能转移贡献,而要转移状态,因为贡献会重复计算;
这题还可以只用bitset,单数貌似过不了洛谷的加强数据
#include <bits/stdc++.h>
using namespace std;
int n;
char a;
bitset<2010> f[2010];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
cin >> a;
f[i][j] = a - '0';
}
f[i][i] = 1;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if(f[j][i])f[j] |= f[i];
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
ans += f[i].count();
}
cout << ans;
return 0;
}
主体部分的双重循环不能调换位置!
第一层一定要是到达的点,这样相当于是让所有能到达i点的j点,都让j包含了i,更新i点的到达点时,一定会更新j点,而调换位置之后,一定会有所遗漏,这与floyd是类似的
1160. 抢掠计划atm
#include <bits/stdc++.h>
using namespace std;
int n,m;
vector <int> e1[500001],e2[500001];
int sz[5000001],scc,iscc[500001],timer,w1[500001],w2[500001],s,p;
bool esc1[500001],esc2[500001],inSTK[500001];
int dfn[500001],low[500001];
stack <int> STK;
int ans;
bool vis[500001];
int f[500001];
void tarjan(int x)
{
dfn[x] = low[x] = ++timer;
STK.push(x);
inSTK[x] = true;
for (int i = 0; i < e1[x].size(); i++)
{
int u = e1[x][i];
if(!dfn[u])
{
tarjan(u);
low[x] = min(low[x],low[u]);
}
else if(inSTK[u])
{
low[x] = min(low[x],dfn[u]);
}
}
if(dfn[x] == low[x])
{
scc++;
int tmp;
do{
tmp = STK.top();
STK.pop();
inSTK[tmp] = false;
iscc[tmp] = scc;
sz[scc]++;
w2[scc] += w1[tmp];
}while(tmp != x);
}
}
int INF = 1e8;
int dfs(int x)
{
if(vis[x]) return f[x];//已经计算好了
vis[x] = 1;
if(esc2[x])f[x] = 0;
else f[x] = -INF;//不能作为终点
for (int i = 0; i < e2[x].size(); i++)
{
int u = e2[x][i];
f[x] = max(f[x],dfs(u));
}
f[x] += w2[x];
return f[x];
}
int main()
{
cin >> n >> m;
int a,b;
for (int i = 1; i <= m; i++)
{
cin >> a >> b;
e1[a].push_back(b);
}
for (int i = 1; i <= n; i++)
{
cin >> w1[i];
}
cin >> s >> p;
for (int i = 1; i <= p; i++)
{
cin >> a;
esc1[a] = true;
}
tarjan(s);
for (int i = 1; i <= n; i++)
{
if(esc1[i])esc2[iscc[i]] = true;
for (int j = 0; j < e1[i].size(); j++)
{
e2[iscc[i]].push_back(iscc[e1[i][j]]);
}
}
ans = dfs(iscc[s]);
cout << (ans < 0 ? 0 : ans);
return 0;
}//cin要改scanf才能A!
缩点之后跑记忆化搜索,此处时使用的dp,还算巧妙
958. 传球
#include <bits/stdc++.h>
using namespace std;
int n,x[200001];
int dfn[200001],low[200001],timer,scc,iscc[200001],in[200001];
int e1[200001],e2[200001];
bool inSTK[200001];
stack<int> STK;
int ans;
void tarjan(int x)
{
dfn[x] = low[x] = ++timer;
inSTK[x] = true;
STK.push(x);
if(!dfn[e1[x]])
{
tarjan(e1[x]);
low[x] = min(low[x],low[e1[x]]);
}
else if(inSTK[e1[x]])
{
low[x] = min(low[x],dfn[e1[x]]);
}
if(low[x] == dfn[x])
{
scc++;
int tmp;
do{
tmp = STK.top();
STK.pop();
iscc[tmp] = scc;
inSTK[tmp] = false;
}while(tmp != x);
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> x[i];
}
sort(x + 1,x + n + 1);
e1[1] = 2;
for (int i = 2; i < n; i++)
{
if(x[i] - x[i - 1] <= x[i + 1] - x[i])
{
e1[i] = i - 1;
}
else
{
e1[i] = i + 1;
}
//cout << e1[i];
}
e1[n] = n - 1;
for (int i = 1; i <= n; i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
for (int i = 1; i <= n; i++)
{
if(iscc[i] == iscc[e1[i]])continue;
//cout << iscc[e1[i]];
in[iscc[e1[i]]]++;
}
for (int i = 1; i <= scc; i++)
{
if(!in[i])ans++;
}
cout << ans;
return 0;
}
自行建图,缩点,入度为0块数即为答案
648. 信息传递
#include <bits/stdc++.h>
using namespace std;
int n,e[200001],timer,dfn[200001],low[200001],scc,sz[200001],iscc[200001];
bool inSTK[200001];
stack <int> STK;
int ans = 1e8;
void tarjan(int x)
{
dfn[x] = low[x] = ++timer;
inSTK[x] = true;
STK.push(x);
if(!dfn[e[x]])
{
tarjan(e[x]);
low[x] = min(low[x],low[e[x]]);
}
else if(inSTK[e[x]])
{
low[x] = min(low[x],dfn[e[x]]);
}
if(dfn[x] == low[x])
{
int tmp;
scc++;
do{
tmp = STK.top();
STK.pop();
inSTK[tmp] = false;
sz[scc]++;
}while(tmp != x);
if(sz[scc] > 1)ans = min(sz[scc],ans);
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> e[i];
}
for (int i = 1; i <= n; i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
cout << ans;
return 0;
}
节点不为1的最小sz
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具