BZOJ 1179: [Apio2009]Atm Tarjan强连通分量缩点,SPFA,DP

Description
这里写图片描述
Input

第一行包含两个整数N、M。N表示路口的个数,M表示道路条数。接下来M行,每行两个整数,这两个整数都在1到N之间,第i+1行的两个整数表示第i条道路的起点和终点的路口编号。接下来N行,每行一个整数,按顺序表示每个路口处的ATM机中的钱数。接下来一行包含两个整数S、P,S表示市中心的编号,也就是出发的路口。P表示酒吧数目。接下来的一行中有P个整数,表示P个有酒吧的路口的编号
Output

输出一个整数,表示Banditji从市中心开始到某个酒吧结束所能抢劫的最多的现金总数。
Sample Input
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
Sample Output
47
HINT

50%的输入保证N, M<=3000。所有的输入保证N, M<=500000。每个ATM机中可取的钱数为一个非负整数且不超过4000。输入数据保证你可以从市中心沿着Siruseri的单向的道路到达其中的至少一个酒吧。

解题方法:
首先进行强连通缩点,维护每一个连通分量中,是否有一个点是酒吧,以及总钱数的多少。对缩点后的图进行拓扑排序,然后按照拓扑排序进行动态规划。记 dp[i]表示走到点 i 的最大获利,对于给定的起点属于的连通块有 dp[i]=money[i],其余点的初值都是 dp[i]=负无穷。对于一条边 i 连接 j,我们用 dp[i]+money[j]来更新 dp[j]。答案是满足一个连通块中至少有一个是酒吧的强连通分量中最大的 dp[i]。由于会爆栈,强连通分量算法要进行人工堆栈。时间复杂度 O(n+m)。

//bzoj 1179 tarjan spfa
#include <bits/stdc++.h>
using namespace std;
const int maxn = 500005;
int n, m, q, S, dfs_clock, cnt, scc, top, ans;
int head1[maxn], head2[maxn], dp[maxn];
int dfn[maxn], low[maxn], c[maxn], v[maxn], s[maxn], belong[maxn];
bool inq[maxn], vis[maxn];
struct edge{
    int v, nxt;
    edge(){}
    edge(int v, int nxt) : v(v), nxt(nxt) {}
}E1[maxn*2], E2[maxn*2];
void init1(){
    cnt = 0;
    memset(dp, 0, sizeof(dp));
    memset(head1, -1, sizeof(head1));
}
void init2(){
    cnt = 0;
    memset(head2, -1, sizeof(head2));
}
void addedge1(int u, int v){
    E1[cnt].v = v, E1[cnt].nxt = head1[u], head1[u] = cnt++;
}
void addedge2(int u, int v){
    E2[cnt].v = v, E2[cnt].nxt = head2[u], head2[u] = cnt++;
}
void tarjan(int x){
    int now = 0;
    dfn[x] = low[x] = ++dfs_clock;
    s[++top] = x; inq[x] = 1;
    for(int i = head1[x]; ~i; i = E1[i].nxt){
        int to = E1[i].v;
        if(!dfn[to]){
            tarjan(to);
            low[x] = min(low[x], low[to]);
        }
        else if(inq[to]){
            low[x] = min(low[x], dfn[to]);
        }
    }
    if(low[x] == dfn[x]){
        scc++;
        while(now != x){
            now = s[top]; top--;
            belong[now] = scc;
            v[scc] += c[now];
            inq[now] = 0;
        }
    }
}
void rebuild(){
    init2();
    for(int i = 1; i <= n; i++){
        for(int j = head1[i]; ~j; j = E1[j].nxt){
            int to = E1[j].v;
            if(belong[i] != belong[to]){
                addedge2(belong[i], belong[to]);
            }
        }
    }
}
void spfa(){
    queue <int> que;
    que.push(belong[S]);
    vis[belong[S]] = 1;
    dp[belong[S]] = v[belong[S]];
    while(!que.empty()){
        int now = que.front(); que.pop(); vis[now] = 0;
        for(int i = head2[now]; ~i; i = E2[i].nxt){
            int to = E2[i].v;
            if(dp[now] + v[to] > dp[to]){
                dp[to] = dp[now] + v[to];
                if(!vis[to]){
                    vis[to] = 1;
                    que.push(to);
                }
            }
        }
    }
}

int main(){
    init1();
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++){
        int u, v;
        scanf("%d%d", &u, &v);
        addedge1(u, v);
    }
    for(int i = 1; i <= n; i++) scanf("%d", &c[i]);
    for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i);
    scanf("%d%d", &S, &q);
    rebuild();
    spfa();
    for(int i = 1; i <= q; i++){
        int x;
        scanf("%d", &x);
        if(dp[belong[x]] > ans) ans = dp[belong[x]];
    }
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值