[APIO2009]抢掠计划 (强连通+SPFA求最长路) HQG_AC的博客

传送门

【题意】:
给一张图,每个点有一个ATM机,而其中的部分点是酒吧(可作为终点)。

抢掠计划就是从起点(1号点)出发,到终点的途中,将ATM机中的钱抢走。

问最多能抢到多少钱。

【注意】:

本题还以一种N<=1000000 的升级版,因为硬件原因(60万以上会爆栈),要写非递归的tarjan,在这里不展开。
【解法】:

因为是有向图,某些点又组成了环,又是让你求最长,综上,很清晰能够想到要用到强连通(不会的点这里)。

首先,有一些ATM机可以互相到达,那这些ATM机的钱可以在走完一次就拿走,这种操作其实就是缩点。

之后,我们重新建图(复杂度不高的),通过SPFA求出每一个点到达S点的最长距离V[i](单源最长路径)。

最后输出酒吧中到达S的V[i]最大的点(最长路)。

AC代码:不信的点这

#include <bits/stdc++.h> 
using namespace std ;
const int N = 500000 + 10 ;
struct node{
    int to,next ;
}G[2*N],edge[2*N];
queue <int> que ;
stack <int> st ;
int n,m,tot,tot1,tot2,a,b,cnt,x,s,p,ans=0 ;
int dfn[N],low[N],belong[N],v[N],f[N],cost[N],head[N],h[N] ;
bool instack[N],inqueue[N] ;
void add_edge(int a,int b){ //初始的建边 
    G[++tot1].to=b ;
    G[tot1].next=head[a] ;
    head[a]=tot1;
}
void add(int a,int b){
    edge[++tot2].to=b ;
    edge[tot2].next=h[a] ;
    h[a]=tot2;
}
void tarjan(int now){ //求强连通分量 
    dfn[now]=low[now]=++tot ;
    st.push(now) ;
    instack[now]=true ;
    for (int i=head[now];i;i=G[i].next) { 
        int to=G[i].to ;//这里写错一次 
        if (!dfn[to]){
            tarjan(to) ;
            low[now]=min(low[now],low[to]) ;
        }
        else {
            if (instack[to]) low[now]=min(low[now],low[to]) ;
        }
    }
    if (dfn[now]==low[now]) {
        cnt++ ;//枚举强连通 
        while (st.top()!=now){
            int t=st.top() ;
            instack[t]=false ;
            belong[t]=cnt ;//属于哪一个强连通分量
            v[cnt]+=cost[t] ; //一个强连通分量合成一个点,因为相互可以到达,所以权值等于所有点的权值和 
            st.pop() ;
        }
        instack[now]=false ;
        belong[now]=cnt ;
        v[cnt]+=cost[now] ;
        st.pop(); 
    }
}
void new_(){
    for (int i=1;i<=n;i++)
    for (int j=head[i];j;j=G[j].next){
        if (belong[i]!=belong[G[j].to]) { //属于不同的强连通,在它们之间建一条边(这里写错一次) 
            add(belong[i],belong[G[j].to]) ; 
        }    
    }
}
void spfa(){ //用SPFA求最长路 
    memset(inqueue,false,sizeof(inqueue)) ;
    int start=belong[s] ;
    que.push(start) ;
    inqueue[start]=true ;
    f[start]=v[start] ;//从起点到start的最长路是v[start] 
    while (!que.empty()){
        int now=que.front() ;que.pop() ;
        for(int i=h[now];i;i=edge[i].next){
            int to=edge[i].to ;
            if (f[now]+v[to]>f[to]) {
                f[to]=f[now]+v[to] ;
                if (!inqueue[to]) {
                    inqueue[to]=true ;
                    que.push(to) ;
                }
            }
        }
        inqueue[now]=false ;
    } 
}
int main(){ 
    scanf("%d %d",&n,&m) ;
    for (int i=1;i<=m;i++){
        scanf("%d%d",&a,&b) ;
        add_edge(a,b) ;
    }
    for (int i=1;i<=n;i++) scanf("%d",&cost[i]) ;//每个ATM机可抢到的钱 
    scanf("%d%d",&s,&p) ;//城市中心,酒吧个数 
    memset(dfn,0,sizeof(dfn)) ;
    memset(instack,false,sizeof(instack)) ;
    for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i) ;
    new_() ;//缩点之后重新建图 
    spfa() ;//求一下最长路 
    for (int i=1;i<=p;i++){
        scanf("%d",&x) ;
        if (f[belong[x]]>ans) ans=f[belong[x]] ;
    }
    printf("%d\n",ans) ;
    return 0 ; 
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值