[題解]P3119 [USACO15JAN] Grass Cownoisseur G

Sinktank發表於2024-11-13

P3119 [USACO15JAN] Grass Cownoisseur G

顯然我們可以先跑強連通分量,由\(x\)個點縮成的新點\(u\)權值為\(v[u]=x\)

下文中的節點\(1\)均表示縮點後節點\(1\)所在的節點。

我們在縮點後的DAG上跑拓撲排序,預處理出\(fa[i]\)\(fb[i]\),分別表示“\(1\)\(i\)路徑的點權和”,“\(i\)\(1\)路徑的點權和”,後者可以建反圖求解。

我們應該找到節點\(u,v\)使得:

  • \(1\)可以到達\(u\)
  • \(u\)\(v\)有邊
  • \(v\)可以到達\(v\)

這樣一個合法的路徑(\(1\)\(u\)\(u\)逆行到\(v\)\(v\)\(i\))就產生了,用\(fa[u]+fb[v]-v[1]\)更新答案即可。

注意,如果整個圖是一個強連通分量,縮點後會只剩\(1\)個點,需要提前用\(v[1]\)更新答案,否則無法透過Subtask #1。

時間複雜度\(O(n+m)\)

點選檢視程式碼
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int n,m,dfn[N],low[N],tim,st[N],top,ans;
int v[N],ori[N],dega[N],degb[N],fa[N],fb[N];
bitset<N> in_stack;
vector<int> G[N],Ga[N],Gb[N];
queue<int> q;
void tarjan(int u){
	dfn[u]=low[u]=++tim;
	st[++top]=u,in_stack[u]=1;
	for(int i:G[u])
		if(!dfn[i])
			tarjan(i),
			low[u]=min(low[u],low[i]);
		else if(in_stack[i])
			low[u]=min(low[u],dfn[i]);
	if(dfn[u]==low[u]){
		while(1){
			int t=st[top--];
			in_stack[t]=0,ori[t]=u,v[u]++;
			if(t==u) break;
		}
	}
}
void topo(vector<int> G[],int deg[],int f[N],int s){
	for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
	memset(f,0,sizeof(int));
	while(!q.empty()){
		int u=q.front();
		q.pop();
		if(u==s||f[u]) f[u]+=v[u];
		for(int i:G[u]){
			f[i]=max(f[i],f[u]);
			deg[i]--;
			if(!deg[i]) q.push(i);
		}
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		G[u].emplace_back(v);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	ans=v[ori[1]];
	for(int i=1;i<=n;i++){
		for(int j:G[i]){
			if(ori[i]!=ori[j]){
				Ga[ori[i]].emplace_back(ori[j]);
				Gb[ori[j]].emplace_back(ori[i]);
				dega[ori[j]]++,degb[ori[i]]++;
			}
		}
	}
	topo(Ga,dega,fa,ori[1]);
	topo(Gb,degb,fb,ori[1]);
	for(int i=1;i<=n;i++){
		if(!fb[i]) continue;
		for(int j:Ga[i]){
			if(!fa[j]) continue;
			ans=max(ans,fb[i]+fa[j]-v[ori[1]]);
		}
	}
	cout<<ans<<"\n";
	return 0;
}

相關文章