C++解题报告:抢掠计划 [ APIO 2009 ] 解析

这篇博客介绍了如何解决一个关于ATM抢劫问题的算法,涉及图论中的强连通分量和SPFA算法。Banditji试图从市中心出发,沿单向道路抢劫ATM,最后到达酒吧。博客提供了输入输出格式、样例及思路,包括利用Tarjan算法求强连通分量和避免DFS导致的超时,推荐使用SPFA算法。

题目描述

Siruseri 城中的道路都是单向的。不同的道路由路口连接。按照法律的规定, 在每个路口都设立了一个 Siruseri 银行的 ATM 取款机。令人奇怪的是,Siruseri 的酒吧也都设在路口,虽然并不是每个路口都设有酒吧。

Banditji 计划实施 Siruseri 有史以来最惊天动地的 ATM 抢劫。他将从市中心 出发,沿着单向道路行驶,抢劫所有他途径的 ATM 机,最终他将在一个酒吧庆 祝他的胜利。

使用高超的黑客技术,他获知了每个 ATM 机中可以掠取的现金数额。他希 望你帮助他计算从市中心出发最后到达某个酒吧时最多能抢劫的现金总数。他可 以经过同一路口或道路任意多次。但只要他抢劫过某个 ATM 机后,该 ATM 机 里面就不会再有钱了。 例如,假设该城中有 6 个路口,道路的连接情况如下图所示:

市中心在路口 1,由一个入口符号→来标识,那些有酒吧的路口用双圈来表

示。每个 ATM 机中可取的钱数标在了路口的上方。在这个例子中,Banditji 能抢 劫的现金总数为 47,实施的抢劫路线是:1-2-4-1-2-3-5。

输入输出格式

输入格式:

 

第一行包含两个整数 N、M。N 表示路口的个数,M 表示道路条数。接下来 M 行,每行两个整数,这两个整数都在 1 到 N 之间,第 i+1 行的两个整数表示第 i 条道路的起点和终点的路口编号。接下来 N 行,每行一个整数,按顺序表示每 个路口处的 ATM 机中的钱数。接下来一行包含两个整数 S、P,S 表示市中心的 编号,也就是出发的路口。P 表示酒吧数目。接下来的一行中有 P 个整数,表示 P 个有酒吧的路口的编号。

 

输出格式:

 

输出一个整数,表示 Banditji 从市中心开始到某个酒吧结束所能抢劫的最多 的现金总数。

 

输入输出样例

输入样例#1: 复制

6 7 
1 2 
2 3 
3 5 
2 4 
4 1 
2 6 
6 5 
10 
12 
8 
16 
1 
5 
1 4 
4 3 5 6

输出样例#1: 复制

47

说明

50%的输入保证 N, M<=3000。所有的输入保证 N, M<=500000。每个 ATM 机中可取的钱数为一个非负整数且不超过 4000。

输入数据保证你可以从市中心 沿着 Siruseri 的单向的道路到达其中的至少一个酒吧。


思路

图中会有强连通分量,他可以在这个分量里抢钱抢完,就直接来一波Tarjan求强连通分量缩点缩点的时候把单点的钱累加,并标记该强连通分量是否有终点(即酒吧),以及出发点,最后走一遍SPFA(不要用DFS,超时,血的教训)

代码详细注释


代码

理解消化。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#define ll long long
#define MAXN 500005
using namespace std ;

vector <int> G[MAXN] ;//初始图 
vector <int> g[MAXN] ;//缩点后的图 
stack <int> S ; 
queue <int> L ;//SPFA 
int m , n , ans ;
int sta , q ;
int w[MAXN] , d[MAXN] ;//w点权,d表示该点所属分量 
bool inQ[MAXN] , inS[MAXN] , end[MAXN] ;//inS是否在栈中 ,vis在SPFA ,inQ是否在队列 
int DFN[MAXN] , low[MAXN] ;//Tarjan用 
int cnt , W[MAXN] , num , be , fsum , f[MAXN] ;//缩点信息 
int w_sum[MAXN] ;//SPFA用 

struct node{
	int from , to ;
}way[MAXN];

void Tarjan( int x ) {//求强连通分量,缩点 
    num ++ ;
	DFN[x] = low[x] = num ;
	S.push(x);
	inS[x] = 1 ;
	for(int i = 0 ; i < G[x].size() ; ++ i ) {
		int s = G[x][i] ;
		if( !DFN[s] ) {
			Tarjan( s );
			low[x] = min( low[x] , low[s] );
		}
		else if( inS[s] )
			low[x] = min( low[x] , DFN[s] );
	}
	if( low[x] == DFN[x] ) {
        cnt ++ ;
        int now ;
        do{
            now = S.top() ;
            S.pop();
            d[now] = cnt ;
            inS[now] = 0 ;
            if( end[now] ) {//标记有终点的分量 
            	fsum ++ ;//会存重复 ,但不会影响结果 
            	f[fsum] = cnt ;//因为都指向同一个分量 
			}
            if( now == sta )
            	be = cnt ;
            W[cnt] += w[now] ;
        }while( now != x );
    }
}

void SPFA() {
	L.push(be) ;
	inQ[be] = 1 ;//入队 ,标记 
	w_sum[be] = W[be] ;
	while( !L.empty() ) {
		int from = L.front() ;
		L.pop() ;
		inQ[from] = 0 ;
		for( int i = 0 ; i < g[from].size() ; ++ i ) {
			int s = g[from][i] ;
			if( w_sum[s] < w_sum[from] + W[s] ) {//松弛一下 
				w_sum[s] = w_sum[from] + W[s] ;
				if( !inQ[s] ) {
					L.push(s) ;
					inQ[s] = 1 ;//入队 ,标记 
				}
			}
		}
	}
}

int main() {
	scanf("%d%d", &n , &m );//基本输入 
	for( int i = 1 ; i <= m ; ++ i ) {
		int a , b ;
		scanf("%d%d", &a , &b );
		G[a].push_back(b);
		way[i].from = a , way[i].to = b ;
	}
	for( int i = 1 ; i <= n ; ++ i )
		scanf("%d", &w[i] );
	scanf("%d%d", &sta , &q );
	for( int i = 1 ; i <= q ; ++ i ) {
		int a ;
		scanf("%d", &a );
		end[a] = 1 ;
	}
	for( int i = 1 ; i <= n ; ++ i ) {//缩点 
        num = 0 ;
		if( !DFN[i] )
			Tarjan( i );
	}
	for( int i = 1 ; i <= m ; ++ i ) {//连接缩点后的图 
		int a = d[way[i].from] ;
		int b = d[way[i].to] ;
		if( a != b )
			g[a].push_back(b); 
	}
	SPFA();
	for( int i = 1 ; i <= fsum ; ++ i )//找最优解 
		ans = max( ans , w_sum[f[i]] );
	printf("%d", ans );
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值