题目描述
输入描述 Input Description
第一行包含两个整数N、M。N 表示路口的个数,M 表示道路条数。接下来
M 行,每行两个整数,这两个整数都在1 到N 之间,第i+1 行的两个整数表示第
i 条道路的起点和终点的路口编号。接下来N 行,每行一个整数,按顺序表示每
个路口处的ATM 机中的钱数。接下来一行包含两个整数S、P,S 表示市中心的
编号,也就是出发的路口。P 表示酒吧数目。接下来的一行中有P 个整数,表示
P 个有酒吧的路口的编号。输出描述 Output Description
输出一个整数,表示Banditji 从市中心开始到某个酒吧结束所能抢劫的最多
的现金总数。样例输入 Sample Input
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样例输出 Sample Output
47数据范围及提示 Data Size & Hint
50%的输入保证N, M<=3000。所有的输入保证N, M<=500000。每个ATM
机中可取的钱数为一个非负整数且不超过4000。输入数据保证你可以从市中心
沿着Siruseri 的单向的道路到达其中的至少一个酒吧。
题解
题意还是比较裸的,在环上的ATM机是一定都要抢的。首先是求强连通分量缩点,然后从起点即市中心开始spfa最长路或者DP,目的是求出最终停在每个强连通分量时所抢得的总钱数;然后读入所有酒吧,把有酒吧的强连通分量作为终点,枚举一下取钱数的最大值即可。
然而数据规模使得递归的tarjan算法被卡住了,所以要手动改成非递归。
首先用一个大栈(可以理解成系统栈的替代品,区分递归tarjan用到的小栈)保存当前正在tarjan的点。设当前正在tarjan点x,则先把x入大栈,同时入小栈,标记x的dfn和low的值,并作出相应标记表示x已经入了小栈(就像递归tarjan一样);
其次,开始非递归主过程:
当大栈非空时,从栈顶取出一个元素t,但不要弹栈;
访问t所有出边指向的结点a,若dfn[a]等于0,就把a压入大栈,相当于递归tarjan中的递归调用tarjan(a),然后break掉,进入下文中的“然后”,因为递归tarjan中此时low(a)的值会在递归过程中算出,非递归tarjan中low(a)的值还不知道;
若dfn[a]不等于0,不急,先什么也不干,下文中“然后”会处理;然后,t所有出边都已被遍历,这时,判断t是否是大栈的栈顶;
如果是,则说明t没有进入任何递归过程,即t所有出边指向的点都已经被tarjan完了,这时应该确定low[t]的值;
访问t所有出边指向的结点a,若dfn[a] > dfn[t],说明上文“其次”中访问a时a的dfn是0,所以执行递归tarjan中调用完tarjan(a)之后的操作——low[t] = min(low[t], low[a]);
若dfn[a]< dfn[t],说明上文“其次”中访问a时a的dfn不是0,所以执行递归tarjan中dfn[a] != 0之后的操作——若a在小栈中则low[t] = min(low[t], dfn[a]);
这样,我们确定了low[t]的值;
如果此时dfn[t] == low[t],则从小栈里取出这个强连通分量,过程和递归tarjan完全相同,故不再赘述,此后才可以在大栈中弹掉t,因为t所属的强连通分量被算出来以后,tarjan(t)才真正结束,t才可以从大栈里取出;
如果t不是大栈的栈顶,则回到上文中的“其次”,相当于递归调用t的出边指向的某个终点;最后,“其次”时发现大栈为空,算法结束。
Code
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
#define nil 0
#define N 500005
using namespace std;
int n, m, csh[N], S, P, f[N];//题目中的变量,csh代表缩点前每个点有多少钱,f[i]是第i个强连通分量为终点能抢多少钱
int u[N << 2], v[N << 2], nxt[N << 2], pnt[N], e;//存原图用的邻接表
int dfn[N], low[N], isin[N], hcash[N], tot, indx;//tarjan算法,hcash[i]表示第i个强连通分量一共有多少钱
bool instack[N], vis[N];//instack存是否在小栈中,vis是spfa用的
int Sta[N], stop;//小栈
vector <int> g[N];//存缩点后的新图用的vector
stack <int> s;//大栈
void add(int a, int b)//向原图中加边
{
u[++e] = a; v[e] = b;
nxt[e] = pnt[a]; pnt[a] = e;
}
/*void tarjan(int k)//递归tarjan
{
dfn[k] = low[k] = ++indx;
instack[k] = true;
Sta[++stop] = k;
for(int j = pnt[k]; j != nil; j = nxt[j])
{
if(dfn[v[j]] == nil)
{
tarjan(v[j]);
low[k] = min(low[k], low[v[j]]);
}
else
{
if(instack[v[j]] && dfn[v[j]] < low[k])
{
low[k] = dfn[v[j]];
}
}
}
if(dfn[k] == low[k])
{
++tot;
int j;
do
{
j = Sta[stop--];
instack[j] = false;
isin[j] = tot;
hcash[tot] += csh[j];
} while(j != k);
}
}*/
void tarjan(int x)//非递归tarjan
{
s.push(x);
dfn[x] = low[x] = ++indx;
Sta[++stop] = x;
instack[x] = true;
while(!s.empty())
{
int t = s.top();
for(int i = pnt[t]; i != nil; i = nxt[i])
//这里可以开一个和ISAP最大流相似的当前弧优化,不过似乎用处不大
{
if(dfn[v[i]] == 0)//如果未访问,则压栈,准备访问
{
dfn[v[i]] = low[v[i]] = ++indx;
Sta[++stop] = v[i]; instack[v[i]] = true;
s.push(v[i]);
break;//一定要break,意思是立即递归调用tarjan(a)
}
}
if(t == s.top())
{
for(int i = pnt[t]; i != nil; i = nxt[i])
{
if(dfn[v[i]] > dfn[t]) low[t] = min(low[t], low[v[i]]);
//这句对应递归tarjan中的if(dfn[v[j]] != 0)的情况
else if(instack[v[i]]) low[t] = min(low[t], dfn[v[i]]);
}
if(dfn[t] == low[t])
{
++tot;
int j;
do
{
j = Sta[stop--];
instack[j] = false;
isin[j] = tot;
hcash[tot] += csh[j];
} while(j != t);
}
s.pop();
}
}
}
void init()
{
int a, b;
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; ++i)
{
scanf("%d%d", &a, &b);
add(a, b);
}
for(int i = 1; i <= n; ++i)
{
scanf("%d", &csh[i]);
}
scanf("%d%d", &S, &P);
for(int i = 1; i <= n; ++i)
{
if(dfn[i] == 0)
{
tarjan(i);
}
}
for(int i = 1; i <= n; ++i)
{
for(int j = pnt[i]; j != nil; j = nxt[j])
{
if(isin[i] != isin[v[j]])
{
g[isin[i]].push_back(isin[v[j]]);
}
}
}
}
void work()//spfa最长路,以前的DP写挫了。。。
{
queue <int> Q;
memset(vis, 0, sizeof(vis));
memset(f, 0, sizeof(f));
f[isin[S]] = hcash[isin[S]];
Q.push(isin[S]);
vis[isin[S]] = true;
while(!Q.empty())
{
int t = Q.front();
Q.pop();
vis[t] = false;
for(int i = 0; i < g[t].size(); ++i)
{
if(f[g[t][i]] < f[t] + hcash[g[t][i]])
{
f[g[t][i]] = f[t] + hcash[g[t][i]];
if(!vis[g[t][i]])
{
vis[g[t][i]] = true;
Q.push(g[t][i]);
}
}
}
}
int ans = 0, a;
for(int i = 1; i <= P; ++i)
{
scanf("%d", &a);
ans = max(ans, f[isin[a]]);
}
printf("%d\n", ans);
}
int main()
{
init();
work();
return 0;
}