题目地址: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];
  1. 前置准备

    前向星存图,基本操作:
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;
}
  1. 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;
}
}
}
  1. 主函数部分

    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;
}
  1. 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需要注意的细节了(其实就一行…)

  1. 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求最长路的更多相关文章

  1. 【题解】洛谷P3627 [APIO2009]抢掠计划(缩点+SPFA)

    洛谷P3627:https://www.luogu.org/problemnew/show/P3627 思路 由于有强连通分量 所以我们可以想到先把整个图缩点 缩点完之后再建一次图 把点权改为边权 并 ...

  2. 洛谷 P3627 [APIO2009]抢掠计划 题解

    Analysis 建图+强连通分量+SPFA求最长路 但要保证最后到达的点中包含酒馆 虽然思路并不难想,但要求的代码能力很高. #include<iostream> #include< ...

  3. [洛谷P3627][APIO2009]抢掠计划

    题目大意:给你一张$n(n\leqslant5\times10^5)$个点$m(m\leqslant5\times10^5)$条边的有向图,有点权,给你起点和一些可能的终点.问从起点开始,到任意一个终 ...

  4. 洛谷 P3627 [APIO2009]抢掠计划

    这题一看就是缩点,但是缩完点怎么办呢?首先我们把所有的包含酒吧的缩点找出来,打上标记,然后建立一张新图, 每个缩点上的点权就是他所包含的所有点的点权和.但是建图的时候要注意,每一对缩点之间可能有多条边 ...

  5. [luogu3627 APIO2009] 抢掠计划 (tarjan缩点+spfa最长路)

    传送门 Description Input 第一行包含两个整数 N.M.N 表示路口的个数,M 表示道路条数.接下来 M 行,每行两个整数,这两个整数都在 1 到 N 之间,第 i+1 行的两个整数表 ...

  6. [APIO2009]抢掠计划 tarjan缩点+spfa BZOJ1179

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

  7. 洛谷 P3627 【抢掠计划】

    题库:洛谷 题号:3627 题目:抢掠计划 link:https://www.luogu.org/problem/P3627 思路 : 这道题是一道Tarjan + 最长路的题.首先,我们用Tarja ...

  8. BZOJ1179或洛谷3672 [APIO2009]抢掠计划

    BZOJ原题链接 洛谷原题链接 在一个强连通分量里的\(ATM\)机显然都可被抢,所以先用\(tarjan\)找强连通分量并缩点,在缩点的后的\(DAG\)上跑最长路,然后扫一遍酒吧记录答案即可. # ...

  9. 洛谷3627 [APIO2009]抢掠计划

    题目描述 输入格式: 第一行包含两个整数 N.M.N 表示路口的个数,M 表示道路条数.接下来 M 行,每行两个整数,这两个整数都在 1 到 N 之间,第 i+1 行的两个整数表示第 i 条道路的起点 ...

随机推荐

  1. java正则匹配 指定内容以外的 内容

    今天,遇到一个需要 匹配出 指定内容以外的 内容的需求. 乍一看,需求貌视很简单啊,直接上 非贪婪模式的 双向零宽断言(有的资料上也叫 预搜索.预查.环视lookaround): 比如,我要匹配 串内 ...

  2. 彻底解决go get golang.org/x等包失败与VSCode golang插件安装失败问题

    由于某种众所周知的一些原因,https://golang.org/ golang 的官方域名是被墙了的,这也就导致了, 在广大 go 开发者使用 golang 的时候,总会出现 go get 失败的问 ...

  3. 【JAVA习题三十】求0—7所能组成的奇数个数

    package erase; public class 求0到7所能组成的奇数个数 { public static void main(String[] args) { /* * 求0—7所能组成的奇 ...

  4. 关于adb命令的基本使用

    在我们使用adb命令之前,我们要安装一个安卓模拟器(夜神.逍遥.海马王......),这里以夜神安卓模拟器为准(个人推荐,没用过可以使用夜神模拟器). 进入夜神安卓模拟器官网:https://www. ...

  5. javaweb之Servlet,http协议以及请求转发和重定向

    本文是作者原创,版权归作者所有.若要转载,请注明出处. 一直用的框架开发,快连Servlet都忘了,此文旨在帮自己和大家回忆一下Servlet主要知识点.话不多说开始吧 用idea构建Servlet项 ...

  6. <WP8开发学习笔记>修改panorama全景控件的标题的大小

    panorama(全景)控件非常具有WinPhone特色,但是那个巨大的标题许多时候会让人觉得违和.怎么修改它呢? 最开始想到的是加一个FontSize,结果毫无影响.╮(╯-╰)╭ <phon ...

  7. 网络聚合Network Teaming

    team是新的聚合软件,依赖于安装包teamd,可以通过nmcli管理. team和bond的区别在于,支持hash加密,支持负载均衡,支持8块网卡,更好地支持IPV6,总之要取代bond. 1. 添 ...

  8. java特性 JDK JRE JVM

    1简单性 2可移植性性(跨平台) 3面向对象 4高性能 5分布式 6动态性 7多线程 8安全性JDK:java开发工具 . JRE:JDK:java运行环境 . JVM:JDK:java虚拟机

  9. 基于Web的监控系统的开发进行分布式和现代生产(外文翻译)

    摘要 近年来,Web技术发展迅速.尤其是网络浏览器增强了其功能因为JavaScript,CSS3和HTML5的改进.因此,功能越来越丰富的基于Web的软件解决方案功能范围可用.通过使用响应式网页设计( ...

  10. 腾讯云Redis混合存储版重磅推出,万字长文助你破解缓存难题!

    导语 | 缓存+存储的系统架构是目前常见的系统架构,缓存层负责加速访问,存储层负责存储数据.这样的架构需要业务层或者是中间件去实现缓存和存储的双写.冷热数据的交换,同时还面临着缓存失效.缓存刷脏.数据 ...