- 题库:洛谷
- 题号:3627
- 题目:抢掠计划
- link:https://www.luogu.org/problem/P3627
思路 : 这道题是一道Tarjan + 最长路的题。首先,我们用Tarjan把每个强连通分量缩成一个点,并记录那个强连通分量的点权和(因为当那个人走进一个强连通分量后那个强连通分量中的所有money都会被他拿走(绕一圈不就完了?)),然后我们化点权为边权,再以起点所在的强连通分量跑最长路,最后就能计算出从起点所在的强连通分量到任意一个终点所在的强连通分量的最长距离了(最大money值),输出的是取从起点所在的强连通分量到任意一个终点所在的强连通分量的最大值。
细节问题 :
- 关于如何把点权转化为边权:很简单,设u -> k有一条路,q[i]表示点i的权值,边的权值就是q[u],最后我们只需把q[最后一个点的权值(终点)]加上就好了。
- 关于如何建边:只需把不在同一个强连通分量中的可以连接的边连起来就好了(注意:是连两个点所在的的强连通分量)。
- 关于如何跑最长路:我们只学过最短路,但没学过该如何跑最长路,所以我们化未知变为已知,把最长路化为最短路。你们仔细想一想,如果我们把边权建为负的,是不是就可以跑最短路了?最后再把距离 * -1 不就完了?
code:
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
stack < int > pru;
int n, m, p, head[], q[], col[], color, dfn[], low[], s, e[], sum[], z, num, vis[], dis[], ans;//变量太多懒得解释,意会一下吧。。。
struct node//存边
{
int next, to, val;
}stu[];
queue < node > G;//用于临时存放边
inline void add(int x, int y, int z)
{
stu[++num].next = head[x];
stu[num].to = y;
stu[num].val = z;
head[x] = num;
return;
}
inline void tarjan(int u)//Tarjan算法,这里用于缩点
{
dfn[u] = low[u] = ++z;
vis[u] = ;
pru.push(u);
for(register int i = head[u]; i; i = stu[i].next)
{
int k = stu[i].to;
if(!vis[k])
{
tarjan(k);
low[u] = min(low[u], low[k]);
}
else if(!col[k])
{
low[u] = min(low[u], dfn[k]);
}
}
if(dfn[u] == low[u])
{
col[u] = ++color;
sum[color] += q[u];//权值和
while(pru.top() != u)
{
col[pru.top()] = color;
sum[color] += q[pru.top()];//权值和
pru.pop();
}
pru.pop();
}
return;
}
inline void spfa(int s)//求最短路模板SPFA
{
queue < int > pru;
memset(vis, , sizeof(vis));
memset(dis, INF, sizeof(dis));
pru.push(s);
dis[s] = ;
vis[s] = ;
while(!pru.empty())
{
int u = pru.front();
pru.pop();
vis[u] = ;
for(register int i = head[u]; i; i = stu[i].next)
{
int k = stu[i].to;
if(dis[k] > dis[u] + stu[i].val)
{
dis[k] = dis[u] + stu[i].val;
if(!vis[k])
{
vis[k] = ;
pru.push(k);
}
}
}
}
return;
}
inline void init()//初始化
{
memset(head, , sizeof(head));
for(register int i = ; i <= ; ++i)
{
stu[i].next = ;
stu[i].to = ;
stu[i].val = ;
}
num = ;
return;
}
inline void add_edge()
{
for(register int u = ; u <= n; ++u)
{
for(register int i = head[u]; i; i = stu[i].next)
{
int k = stu[i].to;
if(col[k] != col[u])//不在一个强连通分量中才建边
{
//需要临时先放到G里,因为如果还没有把那些一大堆东西初始化就又建边,会.......
G.push(node{col[u], col[k], -sum[col[u]]});//要建负边权(这样可以把最长路转化为最短路)
}
}
}
init();//初始化
while(!G.empty())//初始化完再存边
{
node p = G.front();
add(p.next, p.to, p.val);//建边
G.pop();
}
return;
}
signed main()
{
scanf("%d %d", &n, &m);
for(register int i = , x, y; i <= m; ++i)
{
scanf("%d %d", &x, &y);
add(x, y, );
}
for(register int i = ; i <= n; ++i)
{
scanf("%d", &q[i]);
}
scanf("%d %d", &s, &p);
for(register int i = ; i <= p; ++i)
{
scanf("%d", &e[i]);
}
for(register int i = ; i <= n; ++i)
{
if(!vis[i])
{
tarjan(i);
}
}
add_edge();//建边
spfa(col[s]);//从起点所在的强连通分量中开始
for(register int i = ; i <= p; ++i)
{
ans = max(ans, dis[col[e[i]]] * -/*别忘了把它变回正数*/ + sum[col[e[i]]]/*别加上终点所在的强联通分量的权值*/);
}
printf("%d", ans);
return ;
}