zoukankan      html  css  js  c++  java
  • Codeforces Round #627 (Div. 3)

    Contest Info


    Practice Link

    SolvedABCDEF
    5/6 O O O  O  Ø  Ø
    • O 在比赛中通过 
    • Ø 赛后通过
    • ! 尝试了但是失败了
    • - 没有尝试

    Solutions


    E.Sleeping Schedule

    题意:

    在这个故事中,一天有$h$小时,在$[l;r]$之间入睡就能做个好梦。

    现在我从$0$时刻开始,第$i$次要睡觉的时候,我都可以选择$a[i]$或者$a[i]-1$小时后入睡,问睡了$n$次觉的最大好梦数

    思路:

    这是个典型的基础$dp$问题,然而比赛时候我的思路和定义的状态、转移极为混乱

    题解给出一种比较好的做法,我们用$dp[i][j]$表示睡$i$次觉并执行$j$次$-1$操作时睡好觉的最大次数

    类似于背包$dp$,假如我们选择提前入睡,那么$dp[i][j]$就由$dp[i-1][j-1]$得到;我们不选择提前入睡,$dp[i][j]$就由$dp[i-1][j]$转移而来,由此可以得到状态转移方程:

    $$dp[i][j] = max(dp[i-1][j-1], dp[i-1][j])+lleq (sum[i]-j)\%h leq r$$

    需要注意的一点是,当$j$为$0$的时候,很明显当前只能不选择提前入睡

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <map>
    using namespace std;
    const int maxn = 2e3+100;
    int n, h, l, r, ans;
    int a[maxn], dp[maxn][maxn];
    int main(){
        scanf("%d%d%d%d", &n, &h, &l, &r);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]), a[i] += a[i-1]; 
        for(int i = 1; i <= n; i++){
            for(int j = 0; j <= i; j++){
                int t = (a[i]-j)%h;
                if(j) dp[i][j] = max(dp[i-1][j], dp[i-1][j-1])+(l<=t&&t<=r);
                else dp[i][j] = dp[i-1][j]+(l<=t&&t<=r);
            }
        }
        for(int i = 0; i <= n; i++) ans = max(ans, dp[n][i]);
        printf("%d", ans);
    }
    View Code

    还有一种做法类似于我比赛的时候的做法,我们用$dp[i][j]$表示睡$i$次觉,第$i$次入睡时间为$j$时的最大好梦数

    这个转移非常好理解,$dp[i][j]$也是由当前是否提前入睡来转移:

    $$dp[i][j] = max(dp[i-1][(j-a[i]+h)\%h], dp[i-1][(j-a[i]+1+h)\%h])+(lleq jleq r)$$

    重点在于,我们要避免不合法的转移!!!

    也就是说除了$dp[0][0] = 0$外,我们要把$dp$的值设置为$-inf$,原因在于我们开始没有睡觉的时候是从$0$时刻开始的,不可能从其他时刻开始,就应该把其他时刻设置为不合法的情况即$-inf$

    那你可能会有疑惑,为什么上面那种我不需要这样呢?那是因为我状态的定义决定了当前转移所需要的状态的都是合法的

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define inf 0x3f3f3f3f
    using namespace std;
    const int maxn = 2e3+100;
    int n, h, l, r, ans;
    int a[maxn], dp[maxn][maxn];
    int main(){
        scanf("%d%d%d%d", &n, &h, &l, &r);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        memset(dp, -0x3f, sizeof(dp));
        dp[0][0] = 0;
        for(int i = 1; i <= n; i++){
            for(int j = 0; j < h; j++){
                dp[i][j] = max(dp[i-1][(j-a[i]+h)%h], dp[i-1][(j-a[i]+1+h)%h])+(l<=j&&j<=r);
            }
        }
        for(int i = 0; i < h; i++) ans = max(ans, dp[n][i]);
        printf("%d", ans);
    }
    View Code

     F. Maximum White Subtree

    题意:

    给一棵树,树上有一些黑点、白点,对于每个点,选择一个包含该点的连通块,使得白色点的数量比黑色点的数量多的值最大。求每个节点的最值

    思路:

    需要注意的是这里的Subtree是题目中的临时定义,这里的“子树”就是指原图的一个连通子图,因为题目有:The subtree of the tree is the connected subgraph of the given tree. 感谢KisekiPurin2019解答疑惑,可能正是这样友好的交流环境让我十分开心,能坚持把算法学下去。其实官方题解已经比大多数的博主解释的清楚很多了,强烈建议去看Editorial

    这题的解法是换根$dp$

    我们先假设这颗树的根是固定的,令$dp_v$为包含$v$的子树中$cnt_w-cnt_b$的最大值,我们很容易可以得到:

    $$dp_v=a_v+sum_{toin children(v)}max(0, dp_{to})$$

    这样我们就能得到$ans[v]$,但是肯定不会允许我们将每个点当作根全部重新算一遍

    仔细观察一下,假如我们将根从$v$移到$to$,那么哪些值会发生变化?很明显只有$dp_v$和$dp_{to}$

    这个时候,$dp_v=dp_v-max(0, dp_{to})$,$dp_{to}=dp_{to}+max(0,dp_v)$

    这样我们就能通过少量的计算得出每个节点为根时的答案,需要注意的是这两步的先后顺序和$dfs$中的一些小细节

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define pb push_back 
    using namespace std;
    const int maxn = 2e5+100;
    int n, u, v;
    int a[maxn], dp[maxn], ans[maxn];
    vector<int> g[maxn];
    void dfs(int v, int p){
        dp[v] = a[v];
        for(auto to : g[v]){
            if(to==p) continue;
            dfs(to, v);
            dp[v] += max(0, dp[to]); 
        }
    }
    void dfs2(int v, int p){
        ans[v] = dp[v];
        for(auto to : g[v]){
            if(to==p) continue;
            dp[v] -= max(0, dp[to]), dp[to] += max(0, dp[v]);
            dfs2(to, v);
            dp[to] -= max(0, dp[v]), dp[v] += max(0, dp[to]);
        }
    }
    int main(){
        scanf("%d", &n);
        for(int i = 1; i <= n; i++){
            scanf("%d", &a[i]);
            if(a[i]==0) a[i] = -1;
        }
        for(int i = 1; i <= n-1; i++){
            scanf("%d%d", &u, &v);
            g[u].pb(v), g[v].pb(u);
        }
        dfs(1, -1);
        dfs2(1, -1);
        for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
        return 0;
    }
     
    View Code

     


    我是快乐的分割线,庆祝第一次补完整场题目 ~~

     

     

     

  • 相关阅读:
    iOS 解析xml
    带滚动条html,js获取鼠标位置
    iOS ViewController利用代理页面传值
    Android Volley完全解析
    32位linux中无法使用AVD的解决方案
    8年前在公交上被年轻小伙打了,76岁大爷苦练功夫“复仇”成功...网友:大爷,你一定是天蝎座的吧
    退学,离家出走,卖房创业,在他即将烧完最后一笔钱时,获250万元融资
    夏普将在迪拜推出植物工厂种草莓
    国产手机出货量今年要追平苹果三星,到底有多难?
    原生ajax动态添加数据
  • 原文地址:https://www.cnblogs.com/wizarderror/p/12485358.html
Copyright © 2011-2022 走看看