• 【题解】$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;
    }
    
  • 相关阅读:
    第二周学习小结
    第一周小结(^_^)
    VS2010和搜狗快捷键冲突
    解除SQL远程编辑表
    SQLServer2005mssqlserver服务与sqlexpress服务有什么区别
    OEA界面生成学习1 总体浏览
    WPF学习:绑定
    OutLook自动存档
    文件目录学习
    AQTime
  • 原文地址:https://www.cnblogs.com/Bn_ff/p/13232796.html
Copyright © 2020-2023  润新知