洛谷 P3627 [APIO2009](抢掠计划 缩点+spfa)

时间:2022-12-24 15:15:13

题目描述

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

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

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

洛谷 P3627 [APIO2009](抢掠计划   缩点+spfa)

市中心在路口 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.

两个裸的算法。

注意:在强连通中,我们可以直接使用点权进行缩点

但在最长路径中,我们就得将点权等效替换成边权进行计算;

 #include<bits/stdc++.h>
using namespace std;
#define man 500010
/*tarjan*/
int dfn[man],low[man],sta[man],bel[man],msum[man];
int dep=,top=,cnt=;
bool vis[man];
/*edge*/
int head[man<<],num=,dis[man];
struct edge
{
int next,to,dis;
}e[man<<];
inline void add(int from,int to,int dis)
{
e[++num].next=head[from];
e[num].to=to;
e[num].dis=dis;
head[from]=num;
}
inline void clear()
{
memset(e,,sizeof(e));
memset(head,,sizeof(head));
memset(vis,,sizeof(vis));
num=;
}
/*common*/
int n,m,cost[man],id[man],st,p,x[man],y[man];
inline void tarjan(int s)
{
dfn[s]=low[s]=++dep;
vis[s]=;sta[++top]=s;
for(int i=head[s];i;i=e[i].next)
{ int to=e[i].to;
if(!dfn[to])
{ tarjan(to);
low[s]=min(low[s],low[to]);
}
else if(vis[to])
low[s]=min(low[s],dfn[to]);
}
if(low[s]==dfn[s])//需熟练掌握这一段的写法
{ int j;++cnt;
do
{ j=sta[top--];
bel[j]=cnt;//缩点
msum[cnt]+=cost[j];//计算联通块的权值
vis[j]=;
}while(j!=s);
}
} inline void spfa(int s)
{ queue<int >q;
for(int i=;i<=cnt;i++) dis[i]=;
dis[s]=;vis[s]=;q.push(s);
do
{ int u=q.front();q.pop();vis[u]=;
for(int i=head[u];i;i=e[i].next)
{ int to=e[i].to;
if(dis[to]>dis[u]+e[i].dis)
{ dis[to]=dis[u]+e[i].dis;
if(!vis[to])
{ vis[to]=;
q.push(to);
}
}
}
}while(q.size());
} int main()
{
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=;i<=m;i++)
{
cin>>x[i]>>y[i];
add(x[i],y[i],);
}
for(int i=;i<=n;i++)
cin>>cost[i];
cin>>st>>p;
for(int i=;i<=p;i++)
cin>>id[i];
for(int i=;i<=n;i++)
if(!dfn[i])
tarjan(i);
clear();
for(int i=;i<=m;i++)//通过将边权取反求出最长路
{
if(bel[x[i]]!=bel[y[i]])
{
add(bel[x[i]],bel[y[i]],-msum[bel[y[i]]]);
}
}
spfa(bel[st]);
int ans=-;
for(int i=;i<=p;i++)
{
ans=max(ans,-dis[bel[id[i]]]);}
cout<<ans+msum[bel[st]]<<endl;//根据最短路的特点,加上出发点的权值
return ;
}

发现自己的强连通分量写的不熟练,需大量练习。