ACMS训练题0307——燃气管道

描述

リリ是一个爱玩游戏的女孩子。

她今天玩的是一个模拟经营类型的游戏。在这个游戏中,她掌控着一个属于自己的国家。这个国家中共有 n 座城市,编号 1 ∼ n 1\sim n 1n。并且这 n 个城市通过 n-1 条无向道路互相连通。

每座城市都有属于自己的海拔高度,不过リリ的整个国家都处在较为平坦的地带,因此若第 i 座城市海拔高度为 h i h_i hi 的话, h i h_i hi 的取值只会是 0 或 1。リリ为了加强城市间的能源输送能力,她打算在城市 a 与 b 间修建燃气管道以及支柱。具体地,如果我们将树上路径 (a,b) 提出来,将节点首尾相接的话,以 h i h_i hi 的值会构成一段 01 序列。我们将会在这个抽象的序列上部署方案,一个合理的方案需要满足:

  • 所有的管道高度只能为 1 或是 2;
  • 某处的管道高度严格大于该地的海拔高度,即海拔为 1 的地方只能修建高度为 2 的管道,而海拔为 0 的地方管道的高度可以为 1 或是 2。

如图所示,这是一种合理的搭建方案,其中红色表示管道,黑色表示支柱。在一座城市的上方(01 序列中的某个元素处)我们可以搭建一条水平的管道,也可以在此处建立一条 S 形管道,每个 S 形管道由三部分组成:0.5 个单位的水平管道、1 个单位的垂直管道和 0.5 个单位的水平管道。

每一座城市(01 序列中每个元素处)的管道左右都有支柱支撑,支柱的高度等于管道端点的高度。

现在给出管道的单位长度花费 c,以及支柱的单位长度的花费 w,你需要计算在城市 (a,b) 间合理铺设燃气管道的最小开销。具体地,リリ会给出 q 次这样的询问,每次询问形同 ( a i , b i ) (a_i,b_i) (ai,bi),意为询问在城市 a i a_i ai与城市 b i b_i bi 间合理铺设燃气管道的最小开销。

输入

第一行三个整数 n,q,testnum,分别表示城市数、询问数,以及测试点编号。这有可能能够帮助你拿到更高的分数。特别地,对于所有样例,testnum=0。

第二行两个正整数 c,w,表示单位长度的管道以及支柱的花费。

第三行为一个长度为 n 的 01 串,第 i 个数字表示 h i h_i hi

接下来 n-1 行,每行两个整数 u i , v i u_i,v_i ui,vi,表示城市 u i , v i u_i,v_i ui,vi 之间有一条无向道路。

再接下来 q 行,每行两个整数 a i , b i a_i,b_i ai,bi,表示询问在城市 a i a_i ai与城市 $b_i $间合理铺设燃气管道的最小开销。

输出

输出共 q 行。对于每组询问,输出一个正整数表示在城市 a i a_i ai 与城市 b i b_i bi

样例

输入输出
8 3 0
173
2 5
174
00110010
175
1 2
176
2 3
177
3 4
178
4 5
179
5 6
180
6 7
181
7 8
182
1 2
183
3 4
184
1 8
185
186
19
187
34
188
94

解题思路

这题直接看了题解,用倍增dp的方案。(树上dp也多是倍增这一思想,毕竟有多个儿子,如果路径询问肯定是LCA+倍增一起用的套路)

定义 d p [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] dp[i][j][0/1][0/1] dp[i][j][0/1][0/1]是从节点i向上走 2 j 2^j 2j个节点(包括本身),从i开始时的管道高度是 1 / 2 1/2 1/2,结尾时的管道高度是 1 / 2 1/2 1/2。有

d p [ i ] [ j ] [ a ] [ b ] = m i n ( d p [ i ] [ j − 1 ] [ a ] [ 0 ] + d p [ f [ i ] [ j − 1 ] ] [ j − 1 ] [ 0 ] [ b ] − 1 ∗ w + c , d p [ i ] [ j − 1 ] [ a ] [ 1 ] + d p [ f [ i ] [ j − 1 ] ] [ j − 1 ] [ 1 ] [ b ] − 2 ∗ w + c ) dp[i][j][a][b] = min(dp[i][j-1][a][0]+dp[f[i][j-1]][j-1][0][b]-1*w+c, dp[i][j-1][a][1]+dp[f[i][j-1]][j-1][1][b]-2*w+c) dp[i][j][a][b]=min(dp[i][j1][a][0]+dp[f[i][j1]][j1][0][b]1w+c,dp[i][j1][a][1]+dp[f[i][j1]][j1][1][b]2w+c)

其中a,b在01中任取,大概含义就是在 f [ i ] [ j − 1 ] f[i][j-1] f[i][j1]处连接,要不以高度0连接,要不以高度1连接。当然,以高度0连接是需要特判的。

mark

  • 对于 2 j 2^j 2j超出节点i深度的情况的特判,这里就直接continue不处理了

  • long long一定要这个warning一定不要关,不然酿成大祸😭

  • 边界判断还是需要仔细考虑考虑的

code

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
const int logN = 20;
const int root = 1;
int n, q, testnum;
long long c, w;
bool visit[N] = {};
int depth[N] = {};
bool height[N] = {};
pair< int, int > father[N][logN] = {};
long long dp[N][logN][2][2] = {};
vector< int > edge[N] = {};

void dfs(int cur, int f = 1)
{
    visit[cur] = true;
    depth[cur] = depth[f] + 1;
    father[cur][0] = {f, cur};
    dp[cur][0][1][1] = 1 * c + 4 * w;
    if (!height[cur])
    {
        dp[cur][0][0][0] = 1 * c + 2 * w;
        dp[cur][0][0][1] = 2 * c + 3 * w;
        dp[cur][0][1][0] = 2 * c + 3 * w;
    }
    for (int i = 0; i < edge[cur].size(); i++)
    {
        int to = edge[cur][i];
        if (!visit[to])
        {
            dfs(to, cur);
        }
    }
}

void update(array< long long, 2 > &ans, pair< int, int > f, int i, bool &flag)
{
    int a = f.first;
    bool h = height[f.first] || height[f.second];
    long long tmp0 = ans[0];
    long long tmp1 = ans[1];
    ans[1] = tmp1 + dp[a][i][1][1] - (flag ? 2 * w : 0);
    if (!h)
    {
        ans[1] = min(ans[1], tmp0 + dp[a][i][0][1] - (flag ? 1 * w : 0));
    }
    ans[0] = tmp1 + dp[a][i][1][0] - (flag ? 2 * w : 0);
    if (!h)
    {
        ans[0] = min(ans[0], tmp0 + dp[a][i][0][0] - (flag ? 1 * w : 0));
    }
    flag = true;
}

int main()
{
    scanf("%d%d%d", &n, &q, &testnum);
    scanf("%lld%lld", &c, &w);
    string str;
    cin >> str;
    for (int i = 0; i < str.length(); i++)
    {
        height[i + 1] = (str[i] == '1');
    }

    for (int u = 0, v = 0, i = 1; i < n; i++)
    {
        scanf("%d%d", &u, &v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    memset(dp, 0x3F, sizeof(dp));
    dfs(root);
    array< pair< int, int >, 4 > tmp{pair< int, int >{0, 0}, {0, 1}, {1, 0}, {1, 1}};
    for (int j = 1; (1 << j) <= n; j++)
    {
        for (int i = 1; i <= n; i++)
        {
            if (depth[i] <= (1 << j))
            {
                continue;
            }
            int f = father[i][j - 1].first;
            int s = father[i][j - 1].second;
            bool h = height[f] || height[s];
            father[i][j] = father[f][j - 1];
            for (int t = 0; t < 4; t++)
            {
                int a = tmp[t].first;
                int b = tmp[t].second;
                dp[i][j][a][b] = min(dp[i][j][a][b], dp[i][j - 1][a][1] + dp[f][j - 1][1][b] - 2 * w);
                if (!h)
                {
                    dp[i][j][a][b] = min(dp[i][j][a][b], dp[i][j - 1][a][0] + dp[f][j - 1][0][b] - 1 * w);
                }
            }
        }
    }
    for (int a = 0, b = 0, i = 0; i < q; i++)
    {
        scanf("%d%d", &a, &b);
        pair< int, int > fathera, fatherb;
        fathera = {a, a};
        fatherb = {b, b};
        long long ans = 0;
        array< long long, 2 > ans1{}, ans2{};
        bool flaga = false, flagb = false;
        if (depth[a] < depth[b])
        {
            swap(fathera, fatherb);
        }
        int d = depth[fathera.first] - depth[fatherb.first];
        for (int i = logN - 1; i >= 0; i--)
        {
            if ((d & (1 << i)) != 0)
            {
                update(ans1, fathera, i, flaga);
                fathera = father[fathera.first][i];
            }
        }
        if (fathera.first == fatherb.first)
        {
            ans = ans1[1] + 1 * c + 2 * w;
            bool h1 = height[fathera.first];
            bool h2 = height[fathera.second];
            if (!h1)
            {
                ans = min(ans, ans1[1] + 2 * c + 1 * w);
            }
            if (!h1 && !h2)
            {
                ans = min(ans, ans1[0] + 2 * c + 2 * w);
                ans = min(ans, ans1[0] + 1 * c + 1 * w);
            }
            printf("%lld\n", ans);
            continue;
        }
        for (int i = logN - 1; i >= 0; i--)
        {
            if (father[fathera.first][i] != father[fatherb.first][i])
            {
                update(ans1, fathera, i, flaga);
                update(ans2, fatherb, i, flagb);
                fathera = father[fathera.first][i];
                fatherb = father[fatherb.first][i];
            }
        }
        assert(fathera.first == fatherb.first);
        ans = ans1[1] + ans2[1] + 1 * c;
        bool ha = height[fathera.first] || height[fathera.second];
        bool hb = height[fatherb.first] || height[fatherb.second];
        if (!ha)
        {
            ans = min(ans, ans1[0] + ans2[1] + 2 * c);
        }
        if (!hb)
        {
            ans = min(ans, ans1[1] + ans2[0] + 2 * c);
        }
        if (!ha && !hb)
        {
            ans = min(ans, ans1[0] + ans2[0] + 1 * c);
        }
        printf("%lld\n", ans);
    }
    return 0;
}
/*
8 3 0
2 5
00110010
1 2
2 3
3 4
4 5
5 6
6 7
7 8
1 2
3 4
1 8

19
34
94
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值