Codeforces Round #627 (Div. 3)
Contest Info
Solved | A | B | C | D | E | F |
---|---|---|---|---|---|---|
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])+l≤(sum[i]−j)%h≤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); }
还有一种做法类似于我比赛的时候的做法,我们用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])+(l≤j≤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); }
F. Maximum White Subtree
题意:
给一棵树,树上有一些黑点、白点,对于每个点,选择一个包含该点的连通块,使得白色点的数量比黑色点的数量多的值最大。求每个节点的最值
思路:
需要注意的是这里的Subtree是题目中的临时定义,这里的“子树”就是指原图的一个连通子图,因为题目有:The subtree of the tree is the connected subgraph of the given tree. 感谢KisekiPurin2019解答疑惑,可能正是这样友好的交流环境让我十分开心,能坚持把算法学下去。其实官方题解已经比大多数的博主解释的清楚很多了,强烈建议去看Editorial
这题的解法是换根dp
我们先假设这颗树的根是固定的,令dpv为包含v的子树中cntw−cntb的最大值,我们很容易可以得到:
dpv=av+∑to∈children(v)max(0,dpto)
这样我们就能得到ans[v],但是肯定不会允许我们将每个点当作根全部重新算一遍
仔细观察一下,假如我们将根从v移到to,那么哪些值会发生变化?很明显只有dpv和dpto
这个时候,dpv=dpv−max(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; }
我是快乐的分割线,庆祝第一次补完整场题目 ~~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微服务架构学习与思考:微服务拆分的原则
· 记一次 .NET某云HIS系统 CPU爆高分析
· 如果单表数据量大,只能考虑分库分表吗?
· 一文彻底搞懂 MCP:AI 大模型的标准化工具箱
· 电商平台中订单未支付过期如何实现自动关单?
· 精选 4 款免费且实用的数据库管理工具,程序员必备!
· Cursor:一个让程序员“失业”的AI代码搭子
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(6)
· 重生之我是操作系统(七)----内存管理(上)
· .NET 阻止Windows关机以及阻止失败的一些原因