[SMOJ1782]最大利润

97 篇文章 0 订阅
11 篇文章 0 订阅

题目描述

政府邀请了你在火车站开饭店,但不允许同时在两个相连接的火车站开。任意两个火车站有且只有一条路径,每个火车站最多有 50 个和它相连接的火车站。

告诉你每个火车站的利润,问你可以获得的最大利润为多少。

例如下图是火车站网络:

最佳投资方案是在1,2,5,6这4个火车站开饭店可以获得利润为 90

输入格式 1782.in

第一行输入整数 N(100000) ,表示有 N 个火车站,分别用 1,2,,N 来编号。接下来 N 行,每行一个整数表示每个站点的利润,接下来 N1 行描述火车站网络,每行两个整数,表示相连接的两个站点。

输出格式 1782.out

输出一个整数表示可以获得的最大利润。

输入样例 1782.in

6
10
20
25
40
30
30
4 5
1 3
3 4
2 3
6 4

输出样例 1782.out

90


这道题目是树型 DP 的典型题,也是入门难度的,比较容易想。

我们先来分析一下题目的条件。

首先,“任意两个火车站有且只有一条路径”,这意味着这道题目是在一个无环的连通图上进行的。再想想,既然无环,其实我们完全可以指定一个根结点,把图的形式稍微变一下。例如,以下表示方式与样例是等价的。(作图的时候出了点问题,权且看作是一棵以 5 为根的倒过来的树吧)

看, 这就成了一棵树!于是我们的任务变成了可以确定顺序进行的。

“但不允许同时在两个相连接的火车站开”,那么意味着某些结点是选,另一些不选的。很容易会联想到前面的刚学的状压 DP,但是我们想想看,结点的个数这么大,根本连状态都表示不了。用状压 DP 简直就是无稽之谈。

如果我们回忆一下之前“信号灯”之类的题目,在一维的情况下,某一些连续的不能取,我们的解决方案是加维记录当前取哪个。同样的道理,虽然是在树上做 DP,但其实也是选或不选的问题,那么我们不妨分别用 f[root][0] f[root][1] 表示以 root 为根的子树中,选 root 和不选 root 所能获得的最大利润。

转移方程很好理解,如果选了根结点,那么它的所有直接子结点都不能选;而如果不选根结点,它的子结点既可以选也可以不选。于是就有

f[root][0]=max{f[C][0],f[C][1]} for each child C of root
f[root][1]=valueroot+f[C][0] for each child C of root

边界条件就是对于所有叶子结点,取就得到它本身的价值,不取就为 0。整个问题的解就是 max(f[R][0],f[R][1]) ,其中 R 是我们指定的整棵树的一个根结点。

那求解的顺序呢,岂不是很难确定?不,我们可以递归求解!反正每个子问题只求一遍,实际上时间复杂度只是 O(n) 的!

还有一个细节问题:读入建树的时候我们还不知道根结点,对于每一条边也就不确定谁是父亲、谁是儿子,因此要给两个点都加一条到另一个点的边。

但是,遍历的时候就可能访问回去父亲结点,要避免这个问题,可以在递归求解的时候加一个参数 pre ,表示当前结点的父亲结点;或者用一个 bool 数组标记结点是否被访问过。这样就可以完美解决本题啦!

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <vector>

using namespace std;

const int maxn = 1e5 + 100;

int n;
int v[maxn];

vector <int> g[maxn]; //用 vector 代替数组模拟指针,很方便
int dp[maxn][5];

bool vis[maxn];

void dfs(int r) {
    vis[r] = true;
    dp[r][1] = v[r];
    dp[r][0] = 0;
    for (int i = 0; i < g[r].size(); i++)
        if (!vis[g[r][i]]) {
            dfs(g[r][i]);
            dp[r][0] += max(dp[g[r][i]][0], dp[g[r][i]][1]);
            dp[r][1] += dp[g[r][i]][0];
        }
}

int main(void) {
    freopen("1782.in", "r", stdin);
    freopen("1782.out", "w", stdout);
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &v[i]);
    for (int i = 1; i < n; i++) {
        int x, y;
        scanf("%d%d", &x, &y);
        g[x].push_back(y); g[y].push_back(x);
    }
    memset(vis, false, sizeof vis);
    dfs(1);

    printf("%d\n", max(dp[1][0], dp[1][1]));
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值