洛谷 P3627 [APIO2009]抢掠计划 Tarjan缩点+Spfa求最长路
题目地址:https://www.luogu.com.cn/problem/P3627
第一次寒假训练的结测题,思路本身不难,但对于我这个码力蒟蒻来说实现难度不小…考试时肛了将近两个半小时才刚肛出来。我也是吐了
题面
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 个有酒吧的路口的编号。
样例输入:
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
输出格式
输出一个整数,表示 Banditji 从市中心开始到某个酒吧结束所能抢劫的最多 的现金总数。
样例输出:47
思路:
由题给的图可以看出来,图中存在环,而每个边和点都可以重复走,那么我们可以从从环的任意一点起,无限制地到达环上的任意一点,所以我们可以把整个环看成一个点,环点的权是环上点权的和,这很容易联想到Tarjan缩点把原图化成DAG图;
然后题目让求最大的总权值,最常规的做法就是用Spfa跑最长路;
(有位很厉害的学长改装了Dijskra…,并坚信它能跑DAG的最长路,我们戏称为堆劣化Spfa…遗憾的是我并没有学会…)
这样,我们确定了大体的思路:Tarjan缩点+Spfa求最长路
代码的实现(重头戏来了)
emm…看看这堆数组就能明白这题有点恶心
所以我觉得分步来讲比较好
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=5*1e5+10;
int n,m,dis[maxn],money[maxn];
bool inq[maxn];
int from[maxn],to[maxn];
int head[maxn],dfn[maxn],low[maxn],dfs_clock=0;
int sta[maxn],scc_cnt=0,siz[maxn],top=0,belong[maxn],len=0,cnt[maxn];
- 前置准备
前向星存图,基本操作:
struct Edge{
int from,to,next,dis;
}edge[maxn*2];
void Add(int a,int b,int c){
edge[++len].to=b;
edge[len].from=a;
edge[len].next=head[a];
edge[len].dis=c;
head[a]=len;
}
- Tarjan函数 这里我用的是数组模拟栈,即sta数组,top为栈顶; 其他的和普通的Tarjan模板没甚不一样
void Tarjan(int u){
dfn[u]=low[u]=++dfs_clock;
sta[++top]=u;//++top不要写成top++
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(!belong[v]) low[u]=min(low[u],dfn[v]);
}//这里省去了一个bool型vis数组,因为v如果入了栈,belong[v]的值
//会被修改,不再是0,这就跟vis的效果一样
if(dfn[u]==low[u]){
int f=++scc_cnt;//scc_cnt记录强连通分量的个数
while(true){
int x=sta[top--];
belong[x]=f;
siz[f]+=money[x];//siz数组记录每个环的总权值
if(x==u) break;
}
}
}
- 主函数部分
Spfa函数涉及到一些小细节,所以先上主函数再说Spfa
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
//这里最好是开两个数组把边记下来,一会建图会用到
scanf("%d%d",&from[i],&to[i]);
//懒人为了少写一个Add函数,直接用0当权值
Add(from[i],to[i],0);
}
for(int i=1;i<=n;i++) scanf("%d",&money[i]);
for(int i=1;i<=n;i++)//缩点,常规操作
if(!dfn[i]) Tarjan(i);
New();//建图函数,下步就说
int U,P;scanf("%d%d",&U,&P);
Spfa(U);//下下步说
int ans=0;
for(int i=1;i<=P;i++){
//遍历各个酒馆,找最远的那个就行
int x;scanf("%d",&x);
ans=max(ans,dis[belong[x]]);
}
printf("%d\n",ans);
return 0;
}
- New函数
这个函数是建一个缩点后的新图让Spfa跑
void New(){
//为节省空间直接清空原图就好,不用再新开
memset(edge,0,sizeof(edge));
memset(head,0,sizeof(head));
len=0;
for(int i=1;i<=m;i++){
//同环内的点无需存
if(belong[from[i]]!=belong[to[i]]){
//这里我直接存了环的原权,如果愿意也可以存负权跑最短路
Add(belong[from[i]],belong[to[i]],siz[belong[to[i]]]);
}
}
}
可能有人注意到我这里在存边权时只存了belong[to[i]]的权,也就是边指向的那个点所在的环的权,那from所在的环的权又该怎办呢
那么,这就是一会Spfa需要注意的细节了(其实就一行…)
Spfa函数(重重头戏来了)
大部分和Spfa的板子没区别,要点写在了注释里
queue<int> q;
void Spfa(int u){
for(int i=1;i<=scc_cnt;i++){
dis[i]=0;
inq[i]=false;
cnt[i]=0;
}
//这里就是全题最精髓也是最不容易注意的地方...
//首先,我们并不确定起点u的位置,它可能是一个独立的点,也可能
//属于一个环,所以,我们要把u直接当成环处理
int U=belong[u];
//再者,因为我们的边权只存了“去环”的权,那么原环的权就丢失了
//所以这里dis的起点不再是0,而是起始点的环权,避免了权丢失
dis[U]=siz[U];
cnt[U]=1;
inq[U]=1;
q.push(U);
while(!q.empty()){
int uu=q.front();q.pop();
inq[uu]=false;
for(int i=head[uu];i;i=edge[i].next){
int v=edge[i].to;
//求最长路,所以是<,最短路则是>
if(dis[v]<dis[uu]+edge[i].dis){
dis[v]=dis[uu]+edge[i].dis;
if(!inq[v]){
inq[v]=true;
q.push(v);
}
}
}
}
}//每次看到spfa后面这一长串括号就觉得心慌
这样,代码的各部分就完成了。纵观整道题,我们发现思路并不复杂,代码都是由基础的模板拼上的,我们所要重点考虑的是两者结合后会相互对对方产生怎样的影响,这里主要就是Tarjan缩点后对边权的影响
(考试的时候我为了找这个错找了两个小时…完蛋玩意儿)
最后
放上全部代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=5*1e5+50;
int n,m,dis[maxn],money[maxn];
bool inq[maxn];
int from[maxn],to[maxn];
int head[maxn],dfn[maxn],low[maxn],dfs_clock=0;
int sta[maxn],scc_cnt=0,siz[maxn],top=0,belong[maxn],len=0,cnt[maxn];
struct Edge{
int from,to,next,dis;
}edge[maxn*2];
void Add(int a,int b,int c){
edge[++len].to=b;
edge[len].from=a;
edge[len].next=head[a];
edge[len].dis=c;
head[a]=len;
}
void Tarjan(int u){
dfn[u]=low[u]=++dfs_clock;
sta[++top]=u;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(!belong[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int f=++scc_cnt;
while(true){
int x=sta[top--];
belong[x]=f;
siz[f]+=money[x];
if(x==u) break;
}
}
}
queue<int> q;
void Spfa(int u){
for(int i=1;i<=scc_cnt;i++){
dis[i]=0;
inq[i]=false;
cnt[i]=0;
}
int U=belong[u];
cnt[U]=1;dis[U]=siz[U];inq[U]=1;
q.push(U);
while(!q.empty()){
int uu=q.front();q.pop();
inq[uu]=false;
for(int i=head[uu];i;i=edge[i].next){
int v=edge[i].to;
if(dis[v]<dis[uu]+edge[i].dis){
dis[v]=dis[uu]+edge[i].dis;
if(!inq[v]){
inq[v]=true;
q.push(v);
}
}
}
}
}
void New(){
memset(edge,0,sizeof(edge));
memset(head,0,sizeof(head));
len=0;
for(int i=1;i<=m;i++){
if(belong[from[i]]!=belong[to[i]]){
Add(belong[from[i]],belong[to[i]],siz[belong[to[i]]]);
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&from[i],&to[i]);
Add(from[i],to[i],0);
}
for(int i=1;i<=n;i++) scanf("%d",&money[i]);
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i);
New();
int U,P;scanf("%d%d",&U,&P);
Spfa(U);
int ans=0;
for(int i=1;i<=P;i++){
int x;scanf("%d",&x);
ans=max(ans,dis[belong[x]]);
}
printf("%d\n",ans);
return 0;
}
小小萌新第一次写博客,感觉还是比较认真的
如果有大佬看见什么不对的地方还请在法律允许的范围内嘲笑我orz
溜了溜了
洛谷 P3627 [APIO2009]抢掠计划 Tarjan缩点+Spfa求最长路的更多相关文章
- 【题解】洛谷P3627 [APIO2009]抢掠计划(缩点+SPFA)
洛谷P3627:https://www.luogu.org/problemnew/show/P3627 思路 由于有强连通分量 所以我们可以想到先把整个图缩点 缩点完之后再建一次图 把点权改为边权 并 ...
- 洛谷 P3627 [APIO2009]抢掠计划 题解
Analysis 建图+强连通分量+SPFA求最长路 但要保证最后到达的点中包含酒馆 虽然思路并不难想,但要求的代码能力很高. #include<iostream> #include< ...
- [洛谷P3627][APIO2009]抢掠计划
题目大意:给你一张$n(n\leqslant5\times10^5)$个点$m(m\leqslant5\times10^5)$条边的有向图,有点权,给你起点和一些可能的终点.问从起点开始,到任意一个终 ...
- 洛谷 P3627 [APIO2009]抢掠计划
这题一看就是缩点,但是缩完点怎么办呢?首先我们把所有的包含酒吧的缩点找出来,打上标记,然后建立一张新图, 每个缩点上的点权就是他所包含的所有点的点权和.但是建图的时候要注意,每一对缩点之间可能有多条边 ...
- [luogu3627 APIO2009] 抢掠计划 (tarjan缩点+spfa最长路)
传送门 Description Input 第一行包含两个整数 N.M.N 表示路口的个数,M 表示道路条数.接下来 M 行,每行两个整数,这两个整数都在 1 到 N 之间,第 i+1 行的两个整数表 ...
- [APIO2009]抢掠计划 tarjan缩点+spfa BZOJ1179
题目描述 Siruseri 城中的道路都是单向的.不同的道路由路口连接.按照法律的规定, 在每个路口都设立了一个 Siruseri 银行的 ATM 取款机.令人奇怪的是,Siruseri 的酒吧也都设 ...
- 洛谷 P3627 【抢掠计划】
题库:洛谷 题号:3627 题目:抢掠计划 link:https://www.luogu.org/problem/P3627 思路 : 这道题是一道Tarjan + 最长路的题.首先,我们用Tarja ...
- BZOJ1179或洛谷3672 [APIO2009]抢掠计划
BZOJ原题链接 洛谷原题链接 在一个强连通分量里的\(ATM\)机显然都可被抢,所以先用\(tarjan\)找强连通分量并缩点,在缩点的后的\(DAG\)上跑最长路,然后扫一遍酒吧记录答案即可. # ...
- 洛谷3627 [APIO2009]抢掠计划
题目描述 输入格式: 第一行包含两个整数 N.M.N 表示路口的个数,M 表示道路条数.接下来 M 行,每行两个整数,这两个整数都在 1 到 N 之间,第 i+1 行的两个整数表示第 i 条道路的起点 ...
随机推荐
- 用my eclipse 新建hibernate 第一个程序
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库. 今天就来开始建我们的第一个hibernat ...
- [原创][开源] SunnyUI.Net 主题
SunnyUI.Net, 基于 C# .Net WinForm 开源控件库.工具类库.扩展类库.多页面开发框架 Blog: https://www.cnblogs.com/yhuse Gitee: h ...
- 使用jdk1.8 stream特性对参数名称进行排序
在对外对接的时候,通常会碰到签名方式, 然后签名的时候,要求按照参数名称进行排序. 比如参数为 c=22&a=1, 需要将结果排序为a=1&c=22, 然后再进行别的运算. 可以使用j ...
- 关于wifi营销的看过来
亲测可用.对于一个开发者来说,终于如获至宝.详情联系qq2455994690.源码可二开.包括微信一键关注上网,手机验证码上网.
- JavaScript 定时器 取消定时器
感谢:链接(视频讲解很清晰) 定时器:作用主要是一定时间间隔后,做出相关的变化,例如图片轮播. 目录 两种定时器的使用 两种定时器区别 取消定时器的方法 两种定时器的使用: 方法一:setTimeou ...
- (二)POI-创建一个sheet页,并添加行列数据
原文:https://blog.csdn.net/class157/article/details/92800439 1.只创建sheet页 package com.java.poi; import ...
- Android学习笔记Log类输出日志信息
Log类提供的方法 代码示例 .. Log.e(TAG,"[错误信息]"); Log.w(TAG,"[警告信息]"); Log.i(TAG,"[普通信 ...
- 这一次搞懂Spring自定义标签以及注解解析原理
前言 在上一篇文章中分析了Spring是如何解析默认标签的,并封装为BeanDefinition注册到缓存中,这一篇就来看看对于像context这种自定义标签是如何解析的.同时我们常用的注解如:@Se ...
- 循序渐进VUE+Element 前端应用开发(11)--- 图标的维护和使用
在VUE+Element 前端应用中,图标是必不可少点缀界面的元素,因此整合一些常用的图标是非常必要的,还好Element界面组件里面提供了很多常见的图标,不过数量不是很多,应该是300个左右吧,因此 ...
- 8.实战交付一套dubbo微服务到k8s集群(1)之Zookeeper部署
1.基础架构 主机名 角色 ip HDSS7-11.host.com K8S代理节点1,zk1 10.4.7.11 HDSS7-12.host.com K8S代理节点2,zk2 10.4.7.12 H ...