[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;
}