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

posted @   此间无物  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示