Processing math: 100%

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次觉并执行j1操作时睡好觉的最大次数

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

dp[i][j]=max(dp[i1][j1],dp[i1][j])+l(sum[i]j)%hr

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

复制代码
#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[i1][(ja[i]+h)%h],dp[i1][(ja[i]+1+h)%h])+(ljr)

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

也就是说除了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

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

dpv=av+tochildren(v)max(0,dpto)

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

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

这个时候,dpv=dpvmax(0,dpto)dpto=dpto+max(0,dpv)

这样我们就能通过少量的计算得出每个节点为根时的答案,需要注意的是这两步的先后顺序和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
复制代码

 

 


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

 

 

 

 

posted @   sparkyen  阅读(219)  评论(0)    收藏  举报
编辑推荐:
· 微服务架构学习与思考:微服务拆分的原则
· 记一次 .NET某云HIS系统 CPU爆高分析
· 如果单表数据量大,只能考虑分库分表吗?
· 一文彻底搞懂 MCP:AI 大模型的标准化工具箱
· 电商平台中订单未支付过期如何实现自动关单?
阅读排行:
· 精选 4 款免费且实用的数据库管理工具,程序员必备!
· Cursor:一个让程序员“失业”的AI代码搭子
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(6)
· 重生之我是操作系统(七)----内存管理(上)
· .NET 阻止Windows关机以及阻止失败的一些原因
点击右上角即可分享
微信分享提示