1. 程式人生 > 實用技巧 >P3119 [USACO15JAN]Grass Cownoisseur G

P3119 [USACO15JAN]Grass Cownoisseur G

P3119 [USACO15JAN]Grass Cownoisseur G

和大佬同桌一起做的,感謝大佬對我的指導,貼一下Ta的部落格


我的最開始的思路就是DFS,因為想著DFS可以列舉往回走的情況,然而我發現題目讀錯了。逆行相當於建一條反向邊,但是並沒有規定必須在走過的路中逆行,你可以提前建這樣一條反向邊

然而我以為只能在走過的路中逆行,完美爆0

過了很久之後把Tarjan的很多題都刷了,然後找到了這道題,發現我們完全通過縮點把一些環縮成一個點,然後繼續處理

如果沒有這個逆行,我會想著跑最長路(SPFA或者拓撲排序),但是對於這個逆行,即建一條反向邊,應該如何處理呢

沒錯,分層圖。我記得我在我的部落格中單獨寫了一篇文章講分層圖,但是放在那裡面的題比較入門,建議先學一學。因為只需要建一條反向邊,所以我們就只需要兩層圖就夠了

記住,在分層圖中,我們新建的一層圖是不會改變原圖的結構的,後面的每一層圖想較於第一層圖(原圖)來說,是一模一樣的建邊,但是對於這個逆行,對於當前節點 \(i\),到達節點 \(j\) ,應該建一條 \(j\)\(i+cnt\) 的邊,然後跑最長路,整個題就解決了

#include <bits/stdc++.h>
using namespace std;
int n,m,x[520010],y[520010],ti,tot,cnt,ans,top,q[520010];
int dfn[520010],low[520010],dis[520010],vis[520010],num[520010],fir[520010],head[520010];

struct node {
	int to,net,val;
} e[5200010];

inline void add(int u,int v,int w) {
	e[++tot].to=v;
	e[tot].val=w;
	e[tot].net=head[u];
	head[u]=tot;
}

inline void tarjan(int x) {
	dfn[x]=low[x]=++ti;
	q[++top]=x;
	vis[x]=1;
	for(register int i=head[x];i;i=e[i].net) {
		int v=e[i].to;
		if(!dfn[v]) {
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else {
			if(vis[v]) low[x]=min(low[x],dfn[v]);
		}
	}
	if(dfn[x]==low[x]) {
		++cnt;
		while(q[top+1]!=x) {
			fir[q[top]]=cnt;
			num[cnt]++;
			vis[q[top]]=0;
			top--;
		}
	}
}

inline void spfa(int s) {
	for(register int i=1;i<=520000;i++) {
		vis[i]=0;
		dis[i]=-1;
	}
	queue<int> q;
	vis[s]=1;
	dis[s]=0;
	q.push(s);
	while(!q.empty()) {
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(register int i=head[x];i;i=e[i].net) {
			int v=e[i].to;
			if(dis[v]<dis[x]+e[i].val) {
				dis[v]=dis[x]+e[i].val;
				if(!vis[v]) {
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}

int main() {
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d",&x[i],&y[i]);
		add(x[i],y[i],0);
	}
	for(register int i=1;i<=n;i++) {
		if(!dfn[i]) tarjan(i);
	}
	tot=0;
	memset(head,0,sizeof(head));
	for(register int i=1;i<=m;i++) {
		if(fir[x[i]]==fir[y[i]]) continue;
		add(fir[x[i]],fir[y[i]],num[fir[x[i]]]);
		add(fir[x[i]]+cnt,fir[y[i]]+cnt,num[fir[x[i]]]);
		add(fir[y[i]],fir[x[i]]+cnt,num[fir[y[i]]]);
		//注意分層圖的建邊,這題只需要兩層圖 
	}
	spfa(fir[1]); //在縮完點之後的圖中跑最長路 
	printf("%d",dis[fir[1]+cnt]); //第二層圖用來往回走 
	return 0;
}