「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;
}
展开阅读全文

没有更多推荐了,返回首页