POJ 3160 Father Christmas flymouse Tarjon+DP

原题见POJ 3160

题意:已知一个有向图,每点均有权值。选择一个起点开始遍历,选择它可以经过的一些点,使得点权值之和最大。一个点可以经过多次,但权值只算一遍。权值可以为负。

友情提供一组数据:

in:
8 7
14
21
5
50
100
1
7
30
0 1
1 0
3 2
4 2
2 5
6 1
2 7
out:
135

分析

权值为负时,直接标记为0,即不会加这个点的权值,但是照样可以经过。强连通分量缩为一点,求出权值之和为该点权值。构造出若干个有向无环图后,从各个入度为0的点开始遍历。

把父亲结点累加到该点的值放在一个额外数组c[]上,它不影响该点本身的权值b[]。类似于拓扑排序的过程,依次删去入度为0的点,同时把该点的累计值作为下一个节点的额外值,保存到下一个节点上。由于下一结点已经存在额外值,取最大值即可。整个过程类似于从前向后缩点的过程。其实就是DP。最后每个连通图都被缩为一点,该点即在这个子图里的单线最大权值。从中取出最大值即可。

附代码

/*--------------------------------------------
 * File Name: POJ 3160
 * Author: Danliwoo
 * Mail: Danliwoo@outlook.com
 * Created Time: 2016-07-07 19:55:50
--------------------------------------------*/

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
int n, m;
#define N 31000
std::vector<int> v[N], w[N];
int vis[N], flag[N], in[N], ins[N], stk[N], dfn[N], low[N], a[N], b[N], c[N];
int sz, top, clk;

void Tarjon(int x){
    dfn[x] = low[x] = clk++;
    stk[top++] = x;
    ins[x] = vis[x] = 1;
    for(int j = 0;j < v[x].size();j++){
        int y = v[x][j];
        if(!vis[y]){
            Tarjon(y);
            low[x] = min(low[x], low[y]);
        } else if(ins[y])
            low[x] = min(low[x], low[y]);
    }
    if(dfn[x] == low[x]){
        // printf("stk: sz=%d    x%d %d %d\n", sz, x, dfn[x], low[x]);
        do{
            // printf("%d (%d, %d)", stk[top-1], dfn[stk[top-1]], low[stk[top-1]]);
            flag[stk[top-1]] = sz;
            ins[stk[top-1]] = 0;
            b[sz] += a[stk[top-1]]; 
            // printf("b-- %d %d\n", sz, b[sz]);
            top--;
        } while(stk[top] != x);
        sz++;
        // printf("\n");
    }
}

void solve(){
    for(int i = 0;i < n;i++)
        for(int j = 0;j < v[i].size();j++){
            int y = v[i][j];
            if(flag[i] != flag[y]){
                in[flag[y]]++;
                w[flag[i]].push_back(flag[y]);
            }
        }
    // for(int i = 0;i < n;i++)
        // printf("%d %d %d\n", i, flag[i], in[flag[i]]);
    int r = 0, ans = 0;
    memset(vis, 0, sizeof(vis));
    memset(c, 0, sizeof(c));
    while(r < sz){
        for(int i = 0;i < sz;i++) if(!vis[i] && in[i] == 0){
            // printf("top i = %d %d %d\n", i, b[i], c[i]);
            vis[i] = 1; r++;
            // printf("%d %d\n", ans, b[i]+c[i]);
            ans = max(ans, b[i]+c[i]);
            for(int j = 0;j < w[i].size();j++){
                int y = w[i][j];
                in[y]--;
                c[y] = max(c[y], b[i]+c[i]);
            }
        }
    }
    printf("%d\n", ans);
}
void init(){
    for(int i = 0;i < N;i++){
        w[i].clear();
        v[i].clear();
    }
    memset(vis, 0, sizeof(vis));
    memset(ins, 0, sizeof(ins));
    memset(in, 0, sizeof(in));
    memset(b, 0, sizeof(b));
    memset(flag, 0, sizeof(flag));
    clk = top = sz = 0;
}
int main()
{
    while(~scanf("%d%d", &n, &m)){
        for(int i = 0;i < n;i++){
            scanf("%d", &a[i]);
            a[i] = max(0, a[i]);
        }
        init();
        while(m--){
            int x, y;
            scanf("%d%d", &x, &y);
            v[x].push_back(y);
        }
        for(int i = 0;i < n;i++) if(!vis[i])
            Tarjon(i);
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值