[USACO15JAN]Grass Cownoisseur G

传送门


比赛的时候tarjan算法出锅了,赶快来复习一下。

题意:给一个\(n\)个点\(m\)条边的有向图,其中可以选择一条边逆行一次,问在这个基础上如何选择一条回路,使其经过的点最多。(重复经过的点只算一次,\(n,m \leqslant 10^5\))。


首先,一个强连通分量内的点都可以选,那么不妨先用tarjan算法缩点,新图中每个点的权值就是原图中该连通分量中点的个数。

那么问题变成了,如何在一个DAG上选择一条从节点\(x\)开始点权和尽量大的回路,其中可以有一条边逆行一次。


因此可以枚举这条边,对于一条边\(u,v\),只要求出从\(x\)\(u\),和从\(v\)\(x\)的点权和最大的路径。拿\(x\)\(u\)为例,题解中几乎都用的是spfa来求解,但是spfa算法本身的时间复杂度是不稳定的。实际上因为这个图是DAG,因此可以用拓扑排序解决。只不过要拓扑排序两次,先是处理出所有\(x\)走不到的节点,再是从\(x\)开始拓扑排序并更新答案。

至于从\(v\)\(x\),建反图,用同样的算法即可解决。


这题还有一种做法是分层图,将新图复制一分,并且如果有边\(u \to v\),就再连边\(v \to u + n\),代表逆行一次。这样同时也能满足走到第二层后就回不到第一层了,刚好符合只能逆行一次的限制。而且分层图同样是DAG,用拓扑排序来解决最长路问题即可。

#include<bits/stdc++.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 1e5 + 5;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

int n, m;
struct Edge
{
	int nxt, to;
}e[maxn];
int head[maxn], ecnt = -1;
In void addEdge(int x, int y)
{
	e[++ecnt] = (Edge){head[x], y};
	head[x] = ecnt;
}

int dfn[maxn], low[maxn], cnt = 0;
bool in[maxn];
int st[maxn], top = 0, col[maxn], num[maxn], ccol = 0;
In void tarjan(int now)
{
	dfn[now] = low[now] = ++cnt;
	st[++top] = now, in[now] = 1;
	forE(i, now, v)
	{
		if(!dfn[v])
		{
			tarjan(v);
			low[now] = min(low[now], low[v]);
		}
		else if(in[v]) low[now] = min(low[now], dfn[v]);
	}
	if(low[now] == dfn[now])
	{
		int x; ++ccol;
		do
		{
			x = st[top--], in[x] = 0;
			col[x] = ccol, num[ccol]++;
		}while(x != now);
	}
}

Edge e2[maxn];
int head2[maxn], ecnt2 = -1, du[maxn];
In void addEdge2(int x, int y)
{
	e2[++ecnt2] = (Edge){head2[x], y};
	head2[x] = ecnt2;
	du[y]++;
}
In void buildGraph(bool flg)
{
	Mem(head2, -1), ecnt2 = -1, Mem(du, 0);
	for(int u = 1; u <= n; ++u)
		forE(i, u, v)
			if(col[u] ^ col[v]) flg ? addEdge2(col[u], col[v]) : addEdge2(col[v], col[u]);
}

int dis1[maxn], dis2[maxn];
/*bool vis[maxn];
In void spfa(int* dis)
{
	Mem(vis, 0);
	queue<int> q; q.push(col[1]); dis[col[1]] = num[col[1]];
	while(!q.empty())
	{
		int now = q.front(); q.pop();
		vis[now] = 0;
		for(int i = head2[now], v; ~i && (v = e2[i].to); i = e2[i].nxt) 
		{
			if(dis[v] < dis[now] + num[v])
			{
				dis[v] = dis[now] + num[v];
				if(!vis[v]) q.push(v), vis[v] = 1;
			}
		}
	}
}*/
In void topo(int* dis)
{
	queue<int> q;
	for(int i = 1; i <= ccol; ++i) if(!du[i] && i != col[1]) q.push(i);
	while(!q.empty())
	{
		int now = q.front(); q.pop();
		for(int i = head2[now], v; ~i && (v = e2[i].to); i = e2[i].nxt) 
			if(!--du[v] && v != col[1]) q.push(v);
	}
	q.push(col[1]), dis[col[1]] = num[col[1]];
	while(!q.empty())
	{
		int now = q.front(); q.pop();
		for(int i = head2[now], v; ~i && (v = e2[i].to); i = e2[i].nxt) 
		{
			dis[v] = max(dis[v], dis[now] + num[v]);
			if(!--du[v]) q.push(v);
		}
	}
}

int main()
{
	Mem(head, -1), ecnt = -1;
	n = read(), m = read();
	for(int i = 1; i <= m; ++i)
	{
		int x = read(), y = read();
		addEdge(x, y); 
	}
	for(int i = 1; i <= n; ++i) if(!dfn[i]) tarjan(i);
	buildGraph(1), topo(dis1);
	buildGraph(0), topo(dis2);
	int ans = 0;
	for(int u = 1; u <= n; ++u)
		forE(i, u, v)
			if(col[u] ^ col[v])
			{
				if(!dis1[col[v]] || !dis2[col[u]]) continue;
				ans = max(ans, dis1[col[v]] + dis2[col[u]] - num[col[1]]);	
			} 
	write(ans), enter;
	return 0;
}
posted @ 2021-11-23 00:13  mrclr  阅读(42)  评论(0编辑  收藏  举报