「APIO2009」 搶掠計劃 - Tarjan縮點+最長路

題目描述

Siruseri 城中的道路都是單向的。不同的道路由路口連接。按照法律的規定, 在每個路口都設立了一個 Siruseri 銀行的 ATM 取款機。令人奇怪的是,Siruseri 的酒吧也都設在路口,雖然並不是每個路口都設有酒吧。

Banditji 計劃實施 Siruseri 有史以來最驚天動地的 ATM 搶劫。他將從市中心 出發,沿着單向道路行駛,搶劫所有他途徑的 ATM 機,最終他將在一個酒吧慶 祝他的勝利。

使用高超的黑客技術,他獲知了每個 ATM 機中可以掠取的現金數額。他希 望你幫助他計算從市中心出發最後到達某個酒吧時最多能搶劫的現金總數。他可 以經過同一路口或道路任意多次。但只要他搶劫過某個 ATM 機後,該 ATM 機 裏面就不會再有錢了。 例如,假設該城中有 6 個路口,道路的連接情況如下圖所示:

圖1

輸入格式

第一行包含兩個整數N、M。N表示路口的個數,M表示道路條數。

接下來M行,每行兩個整數,這兩個整數都在1到N之間,第i+1行的兩個整數表示第i條道路的起點和終點的路口編號。

接下來N行,每行一個整數,按順序表示每個路口處的ATM機中的錢數。

接下來一行包含兩個整數S、P,S表示市中心的編號,也就是出發的路口。P表示酒吧數目。

接下來的一行中有P個整數,表示P個有酒吧的路口的編號。

輸出格式

輸出一個整數,表示Banditji從市中心開始到某個酒吧結束所能搶劫的最多的現金總數。

數據範圍

50%的輸入保證N, M<=3000。

所有的輸入保證N, M<=500000。每個ATM機中可取的錢數爲一個非負整數且不超過4000。

輸入數據保證你可以從市中心沿着Siruseri的單向的道路到達其中的至少一個酒吧。

分析

首先,容易想到這是要讓我們求一個有向圖中的最長路,但不太一樣的是每一個點經過一次後就沒辦法再增加路徑,又想到圖中可以有環,因此可以先將圖縮點,然後就變成了一個DAG;再在這上面跑最長路,Spfa和Dp皆可,因爲這裏沒有環,所以在一條路徑上是不會有重複的點;最後再枚舉到達的酒吧,找答案。

思路比較簡單,但是出題人卡了遞歸的Tarjan,所以只能寫非遞歸的了。

代碼

#include <iostream>
#include <cstdio>
#include <queue>
#include <stack>
using namespace std;
const int N=500005,M=500005;
struct Edge {
	int to,nxt;
}e[M],e1[M];
int h[N],cnt;
int h1[N],cnt1;
int v[N],n,m,ans;
int vv[N],s,vis[N];
int p,gp[N];
int dfn[N],low[N],num;
int co,bel[N],st[N],top;
int instack[N],f[N];
queue<int> q;
stack<int> ss;
void Add(int x,int y) {
	e[++cnt]=(Edge){y,h[x]};
	h[x]=cnt;
}
void Add1(int x,int y) {
	e1[++cnt1]=(Edge){y,h1[x]};
	h1[x]=cnt1;
}
void Init() {
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		Add(u,v);
	}
	for (int i=1;i<=n;i++)
		scanf("%d",&v[i]);
	scanf("%d%d",&s,&p);
	for (int i=1;i<=p;i++)
		scanf("%d",&gp[i]);
}
void Tarjan(int x) {//非遞歸Tarjan
	ss.push(x);
	dfn[x]=low[x]=++num;
	st[++top]=x;
	instack[x]=1;
	while (!ss.empty()) {
		int t=ss.top();
		for (int i=h[t];i;i=e[i].nxt) {
			int y=e[i].to;
			if (!dfn[y]) {
				dfn[y]=low[y]=++num;
				st[++top]=y;
				instack[y]=1;
				ss.push(y);
				break;
			}
		}
		if (t==ss.top()) {
			for (int i=h[t];i;i=e[i].nxt) {
				int y=e[i].to;
				if (dfn[y]>dfn[t]) low[t]=min(low[t],low[y]);
				else if (instack[y]) low[t]=min(low[t],dfn[y]);
			}
			if (dfn[t]==low[t]) {
				int tt;
				++co;
				do {
					tt=st[top--];
					instack[tt]=0;
					bel[tt]=co;
					vv[co]+=v[tt];
				} while (t!=tt);
			}
			ss.pop();
		}
	}
}
//void Tarjan(int x) {//遞歸Tarjan,會爆棧
//	dfn[x]=low[x]=++num;
//	instack[x]=1;
//	st[++top]=x;
//	for (int i=h[x];i;i=e[i].nxt) {
//		int y=e[i].to;
//		if (!dfn[y]) {
//			Tarjan(y);
//			low[x]=min(low[x],low[y]);
//		} else if (instack[y])
//			low[x]=min(low[x],dfn[y]);
//	}
//	if (low[x]==dfn[x]) {
//		int t;
//		++co;
//		do {
//			t=st[top--];
//			instack[t]=0;
//			bel[t]=co;
//			vv[co]+=v[t];
//		} while (t!=x);
//	}
//}
void Work() {
	for (int i=1;i<=n;i++)
		if (!dfn[i]) Tarjan(i);
	for (int x=1;x<=n;x++)//建新圖
		for (int i=h[x];i;i=e[i].nxt) {
			int y=e[i].to;
			if (bel[x]==bel[y]) continue;
			Add1(bel[x],bel[y]);
		}
	q.push(bel[s]);//Spfa最長路
	f[bel[s]]=vv[bel[s]];
	vis[bel[s]]=1;
	while (!q.empty()) {
		int t=q.front();
		q.pop();
		vis[t]=0;
		for (int i=h1[t];i;i=e1[i].nxt) {
			int y=e1[i].to;
			if (f[y]<f[t]+vv[y]) {
				f[y]=f[t]+vv[y];
				if (!vis[y]) {
					vis[y]=1;
					q.push(y);
				}
			}
		}
	}
	for (int i=1;i<=p;i++) {
		ans=max(ans,f[bel[gp[i]]]);
	}
	printf("%d",ans);
}
int main() {
	Init();
	Work();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章