Codeforces Round 958 (Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/1988
感觉这场要掉一百分妈的纯傻逼我是
A
签到
显然一种最优的策略是每次操作分出 个 1,然后考虑最后是否会剩下一个单独的 1。
复制复制// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long //============================================================= //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { int n, k; std::cin >> n >> k; std::cout << ceil(1.0 * (n - 1) / (k - 1)) << "\n"; } return 0; }
B
结论
发现 1 的个数在操作后是单调递减的。
则一种显然的策略是首先对全 0 连续段进行操作使它们均变为一个 0,先尽可能减少 0 的个数,然后判断是否有 即可。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long //============================================================= //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { int n; std::cin >> n; std::string s; std::cin >> s; int c1 = 0, c0 = 0; for (int i = 0; i < n; ++ i) { if (s[i] == '1') ++ c1; if (s[i] == '0' && (i == 0 || s[i - 1] != '0')) ++ c0; } std::cout << (c1 > c0 ? "YES\n" : "NO\n"); } return 0; }
C
二进制
显然数列中最大的数为 ,考虑反向构造,根据 构造 。
为了保证数列递增,则 与 从高向低位中第一个不同的位置,一定 该位为 0,该位为 1。为了保证 ,发现上述的不同的位一定在 中该位为 1,且各位不能重复产生贡献,则除 之外,可构造出的数列元素个数即为 中 1 的个数。
记 中从高到低各二进制位为 ,则一种显然的构造是:
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long #define lowbit(x) ((x)&(-x)) //============================================================= //============================================================= //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { LL n; std::cin >> n; std::vector<LL> ans; ans.push_back(n); LL temp = n; while (temp) { LL b = lowbit(temp); temp -= b; if (n - b) ans.push_back(n - b); } std::cout << ans.size() << "\n"; std::reverse(ans.begin(), ans.end()); for (auto x: ans) std::cout << x << " "; std::cout << "\n"; } return 0; }
D
树形 DP。
发现每个怪物会造成伤害的次数即该怪物是在第几轮被干掉的。保证每轮不选择两个相邻的怪物即保证两个怪物被干掉的轮数不同。
设最多进行 轮后可以干掉所有怪物,则题目等价于为每个节点 分配 中的一个权值 作为系数,表示该节点上怪物是第几轮被干掉的,满足相邻两个节点系数不同,最小化:
然后就变成了这个题。这东西显然可以直接大力 地树形 DP。设 表示令节点 系数为 时,以 为根的子树价值之和的最小值,初始化 ,则有转移:
答案即为:
转移的时候前后缀最值优化一下可以变成 。
然后猜一下 的数量级,随手写个 20 左右就能过了呃呃,然而赛时以为 3 就行 WA 成狗了。可以证明:,证明详见官方题解:https://codeforces.com/blog/entry/131567。
则总时间复杂度 级别或 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL __int128 const int kN = 3e5 + 10; const int kM = kN << 1; //============================================================= int n; long long a[kN]; int edgenum, head[kN], v[kM], ne[kM]; LL f[kN][21]; //============================================================= void addedge(int u_, int v_) { v[++ edgenum] = v_; ne[edgenum] = head[u_]; head[u_] = edgenum; } void dfs(int u_, int fa_) { for (int i = 1; i <= 20; ++ i) { f[u_][i] = 1ll * i * a[u_]; } for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_) continue; dfs(v_, u_); for (int j = 1; j <= 20; ++ j) { LL minf = (j == 1 ? f[v_][2] : f[v_][1]); for (int k = 1; k <= 20; ++ k) { if (j == k) continue; minf = std::min(minf, f[v_][k]); } f[u_][j] += minf; } } } inline void print(LL n) { if(n > 9) print(n / 10); putchar(n % 10 + '0'); } //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n; for (int i = 1; i <= n; ++ i) std::cin >> a[i]; edgenum = 0; for (int i = 1; i <= n; ++ i) head[i] = 0; for (int i = 1; i < n; ++ i) { int u_, v_; std::cin >> u_ >> v_; addedge(u_, v_), addedge(v_, u_); } dfs(1, 0); LL ans = f[1][1]; for (int i = 1; i <= 20; ++ i) { ans = std::min(ans, f[1][i]); } // std::cout << ans << "\n"; print(ans); // std::cout << "\n"; printf("\n"); } return 0; }
E
笛卡尔树。
学习了!
答案即为对于每个权值求有多少个区间以其为最小值,经典单调栈/笛卡尔树问题,建笛卡尔树后,用节点的权值和子树大小计算求和即可。
考虑扔到笛卡尔树上考虑删数后的询问。手玩下被影响的区间位置,发现删去一个节点后,该节点子树外的贡献无影响,对于该子树受影响的仅有其左儿子的右链,和右儿子的左链受到影响,且影响为将这两条按深度权值递减的链,上面的节点按照权值大小归并为一条新的链。
发现对于每个节点均枚举左儿子的右链和右儿子的左链,会且仅会枚举每个点 级别,则考虑 dfs 整棵笛卡尔树并每次直接暴力枚举求链合并后答案的增量即可,具体贡献形式详见代码。总时间复杂度 级别。
// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long #define pr std::pair #define mp std::make_pair const int kN = 5e5 + 10; //============================================================= int n, a[kN]; int top, st[kN]; int rt, son[kN][2]; LL sum, sz[kN], val[kN], ans[kN]; //============================================================= void init() { std::cin >> n; for (int i = 1; i <= n; ++ i) std::cin >> a[i]; for (int i = 0; i <= n; ++ i) son[i][0] = son[i][1] = 0; st[top = 0] = 0; for (int i = 1; i <= n; ++ i) { while (top && a[st[top]] > a[i]) -- top; son[i][0] = son[st[top]][1], son[st[top]][1] = i; st[++ top] = i; } rt = st[1]; } void dfs1(int u_) { sz[u_] = 1; for (int i = 0; i < 2; ++ i) { if (!son[u_][i]) continue; dfs1(son[u_][i]); sz[u_] += sz[son[u_][i]]; } val[u_] = 1ll * (sz[son[u_][0]] + 1ll) * (sz[son[u_][1]] + 1ll) * a[u_]; sum += val[u_]; } void dfs2(int u_, LL delta_) { LL newans = sum - delta_ - val[u_]; std::vector<int> lrchain, rlchain; std::vector<pr<int, int> > chain; for (int v = son[u_][0]; v; v = son[v][1]) newans -= val[v], lrchain.push_back(v); for (int v = son[u_][1]; v; v = son[v][0]) newans -= val[v], rlchain.push_back(v); int i = 0, j = 0, szlr = lrchain.size(), szrl = rlchain.size(); while (i < szlr && j < szrl) { if (a[lrchain[i]] < a[rlchain[j]]) chain.push_back(mp(lrchain[i], 0)), ++ i; else chain.push_back(mp(rlchain[j], 1)), ++ j; } while (i < szlr) chain.push_back(mp(lrchain[i], 0)), ++ i; while (j < szrl) chain.push_back(mp(rlchain[j], 1)), ++ j; LL sumsz = 0; for (i = chain.size() - 1; i >= 0; -- i) { int p = chain[i].first, q = chain[i].second; newans += (sumsz + 1ll) * (sz[son[p][q]] + 1ll) * a[p]; sumsz += sz[son[p][q]] + 1; } ans[u_] = newans; for (i = 0; i < 2; ++ i) { if (son[u_][i]) { dfs2(son[u_][i], delta_ + 1ll * (sz[son[u_][i ^ 1]] + 1) * a[u_]); } } } //============================================================= int main() { //freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { init(); sum = 0, dfs1(rt), dfs2(rt, 0); for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " "; std::cout << "\n"; } return 0; }
写在最后
学到了什么:
- D:能跑过去就尽力扩大上界!
- E:每个权值求有多少个区间以其为最小值,考虑在笛卡尔树上解决。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现