zoukankan      html  css  js  c++  java
  • 【题解】$test0628$ 大逃杀

    ybt 1774 大逃杀

    题目描述

    将地图上的所有地点标号为 (1)(n) ,地图中有 (n−1) 条双向道路连接这些点,通过一条双向道路需要一定时间,保证从任意一个点可以通过道路到达地图上的所有点。

    有些点上可能有资源,到达一个有资源的点后,可以获取资源来增加 (w_i) 的武力值。资源被获取后就会消失,获取资源不需要时间。可选择不获取资源。

    有些点上可能有敌人,到达一个有敌人的点后,必须花费 (t_i) 秒与敌人周旋,并将敌人消灭。敌人被消灭后就会消失。不能无视敌人。

    如果一个点上既有资源又有敌人,必须先消灭敌人才能获取资源。

    游戏开始时Y君可以空降到任意一个点上,接下来,有 (T) 秒时间行动,Y君希望游戏结束时,武力值尽可能大。

    (\)


    (\)

    (Solution)

    奇怪——の前言

    这!道!题!真!的!太!妙!辣!

    由于太妙了所以看完之后总有虚幻之感(?感觉看题解代码跟吸毒似的,上一秒飘飘然下一秒就忘了(?

    所以为了深刻贯彻周树人的“拿来主义”精神,把这道题变成“我的”, 真正地弄懂这道题,于是来写篇题解吧!

    (\)

    正解

    首先,发现这是个无根树,所以大致有两个方向,一个是换根,一个是用一些奇怪的状态包揽所有情况

    先考虑用状态包含所有情况

    正常能想到的有两个状态,(f[i][j]) 表示从 (i) 往子树内走并且回到 (i)(g[i][j]) 表示从 (i) 往子树内走但不用回到 (i)

    但是 (i) 还可以往父亲结点走,这怎么设状态?显然,假设 (i) 往外走到 (j) ,那么这条路上一定有一个转折点 (LCA(i, j)) ,若 (j = LCA(i, j)) 就包含在上面的 (g[LCA(i, j)][j]) 中了

    由于考虑要在 (i) 结点设状态,所以就设 (h[i][j]) 表示从 (i) 子树中某个结点走到 (i) 再走到子树中另一个结点

    是否包含了所有状态?建议多画图想想。严谨证明嘛...待以后填坑233

    (\)

    与换根千丝万缕的联系

    为什么这道看似要换根的题目,直接用三个状态简洁明了的就实现了?

    一般来说,换根都会分 (up)(down) 两个方面分别考虑 (i) 子树外和 (i) 子树内,上面的 (f[i][j])(g[i][j]) 就是 (down) 这部分

    而在这道题中,对于 (i) 结点,(up_i) 其实就是 (h[LCA(i,k)][j]) ,只不过计算 (up_i) 的时候并不是递归到 (i) 点时,是在 (LCA(i, k)) 那里是就考虑完了。

    也就是说,(up) 在这道题里并不是精准的,一个 (h[i][j]) 的状态其中包含了很多的 (up_i) ,一个 (up_i) 又包含了很多个 (h[LCA(i, k)][j]) ,相当于把 (up_i) 拆散了计算的。

    为什么可以这样做?

    归根结底来说,是因为这道题中并不特地强调根节点的作用,而只强调在树中的路径。

    而在 ybt 1773 消息传递ybt 1771 仓库选址 中,它们所求的答案都要求了必须确定根节点,这就要求我们必须通过换根精确地得到结点的值,所以不能拆开计算,再统计答案。

    (\)

    具体实现

    每次我碰上这种两个及两个以上的状态互相更新的时候,往往都会东添西补两三个小时以上,最后重构代码...

    所以趁这道题来尝试着缕清一下,各个状态之间是如何转移的,怎样才能写得完整且逻辑清晰。

    .....................好了我咕了..........理不清楚kkk,等过一段时间再填坑吧反正题解没人看233
    (\)

    完结撒花✿✿ヽ(°▽°)ノ✿

    (\)


    (\)

    (Code)

    以下代码参考 此位大佬 der!

    #include<bits/stdc++.h>
    #define F(i, x, y) for(int i = x; i <= y; ++ i)
    using namespace std;
    int read();
    const int N = 305;
    int n, T, u, v, e, ans;
    int t[N], w[N];
    int f[N][N], g[N][N], h[N][N];
    int head[N], cnt, ver[N << 1], edge[N << 1], nxt[N << 1];
    void add(int x, int y, int z){ver[++ cnt] = y, edge[cnt] = z, nxt[cnt] = head[x], head[x] = cnt;}
    void dfs(int x, int fa)
    {
       for(int i = head[x]; i; i = nxt[i])
          if(ver[i] != fa)
          {
             dfs(ver[i], x);
             for(int j = T; j >= t[x]; -- j)
             {
                for(int k = j - edge[i]; k >= t[x]; -- k)
                /*为什么 k 也要逆序枚举?当 edge[i] 为 0 时,如果顺序枚举,那 f[x][j] 
                    等都被 k 更新完了,最后 k=j 时就无法再用原来的 f[x][j] 等更新了*/
                {
                   int tmp = j - k - edge[i];
                   int fx = f[x][k], gx = g[x][k], hx = h[x][k];
                   /*为什么要提前记录函数值?不完全是为了代码简洁。当 edge[i] 为 0 时,
                       fx,gx,hx 是可能被更新的,如果要用它们自己原本的值更新自己,就  
                      要提前记录原本的值*/
                   if(tmp >= t[ver[i]])
                   {
                      g[x][j] = max(g[x][j], g[ver[i]][tmp] + fx);
                      h[x][j] = max(h[x][j], g[ver[i]][tmp] + gx);
                   }
                   tmp -= edge[i];
                   if(tmp >= t[ver[i]])
                   {
                      g[x][j] = max(g[x][j], f[ver[i]][tmp] + gx);
                      f[x][j] = max(f[x][j], f[ver[i]][tmp] + fx);
                      h[x][j] = max(h[x][j], f[ver[i]][tmp] + hx);
                      h[x][j] = max(h[x][j], h[ver[i]][tmp] + fx);
                   }
                }
                ans = max(ans, h[x][j]);
             }
          }
    }
    int main()
    {
       n = read(), T = read();
       F(i, 1, n) w[i] = read();
       F(i, 1, n)
       {
          t[i] = read();   
          if(t[i] > T) continue;
          f[i][t[i]] = g[i][t[i]] = h[i][t[i]] = w[i];//提前初始化不容易忘
       }
       F(i, 1, n - 1) u = read(), v = read(), e = read(), add(u, v, e), add(v, u, e);
       dfs(1, 0), printf("%d", ans);
       return 0;
    }
    /*--------------- Bn_ff 2020.7.3 ybt1774 ---------------*/
    int read()
    {
       int x = 0, f = 1;
       char c = getchar();
       while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
       while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
       return x * f;
    }
    
  • 相关阅读:
    php多态
    ssl certificate problem: self signed certificate in certificate chain
    test plugin
    open specific port on ubuntu
    junit vs testng
    jersey rest service
    toast master
    use curl to test java webservice
    update folder access
    elk
  • 原文地址:https://www.cnblogs.com/Bn_ff/p/13232796.html
Copyright © 2011-2022 走看看