Codeforces Round 906 (Div. 2)
A. Doremy's Paint 3
对于式子
对于
情况可以分为以下三类:
- 所有的数都一样,比如
,此时答案为 Yes - 有两种不同的数,比如
。如果其中一种数出现了恰好 次则答案为 Yes。例如 和 答案为 Yes 而 和 答案为 No。
时间复杂度为
参考代码
#include <cstdio> const int N = 100005; int a[N], cnt[N]; int main() { int t; scanf("%d", &t); while (t--) { int n; scanf("%d", &n); int diff_num = 0; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); cnt[a[i]]++; if (cnt[a[i]] == 1) diff_num++; } if (diff_num == 1) printf("Yes\n"); else if (diff_num > 2) printf("No\n"); else if (cnt[a[1]] == n / 2 || cnt[a[1]] == (n + 1) / 2) printf("Yes\n"); else printf("No\n"); for (int i = 1; i <= n; i++) cnt[a[i]]--; } return 0; }
B. Qingshan Loves Strings
有三种情况答案为 Yes:
- s 本身是好的
- t 是好的,t 的头尾都是 '1',而 s 中没有 "11"
- t 是好的,t 的头尾都是 '0',而 s 中没有 "00"
参考代码
#include <cstdio> const int N = 55; char s1[N], s2[N]; bool good(char s[], int len) { for (int i = 1; i < len; i++) if (s[i] == s[i - 1]) return false; return true; } bool check(char s[], int len, char ch) { for (int i = 1; i < len; i++) if (s[i] == ch && s[i - 1] == ch) return false; return true; } int main() { int t; scanf("%d", &t); while (t--) { int n, m; scanf("%d%d%s%s", &n, &m, s1, s2); bool res = good(s1, n); if (res) printf("Yes\n"); else { res = good(s2, m); if (!res || s2[0] != s2[m - 1]) printf("No\n"); else if (check(s1, n, s2[0])) printf("Yes\n"); else printf("No\n"); } } return 0; }
C. Qingshan Loves Strings 2
首先,如果
否则,可以按如下方法构造:
如果
如果
"110010"
"1001"
"011001"
"1100"
"10"
""
这样一来实际上将插入
实际上最坏只需要
时间复杂度为
参考代码
#include <cstdio> #include <deque> using namespace std; const int N = 105; char s[N]; int op[N]; int main() { int t; scanf("%d", &t); while (t--) { int n; scanf("%d%s", &n, s); int cnt0 = 0, cnt1 = 0; for (int i = 0; i < n; i++) if (s[i] == '0') cnt0++; else cnt1++; if (cnt0 != cnt1) printf("-1\n"); else { deque<char> dq; for (int i = 0; i < n; i++) dq.push_back(s[i]); int p = 0, del = 0; while (!dq.empty()) { if (dq.front() != dq.back()) { dq.pop_front(); dq.pop_back(); del++; } else { p++; if (dq.front() == '0') { op[p] = n - del; dq.push_back('0'); dq.push_back('1'); } else { op[p] = del; dq.push_front('1'); dq.push_front('0'); } n += 2; } } if (p == 0) printf("0\n\n"); else { printf("%d\n", p); for (int i = 1; i <= p; i++) printf("%d%c", op[i], i == p ? '\n' : ' '); } } } return 0; }
D. Doremy's Connecting Plan
不妨假设
设
如果
而这实际上可以推出
而且,添加一条新的边不会导致原来能加的边变得不能加,所以我们可以永远考虑
对于式子
时间复杂度为
参考代码
#include <cstdio> #include <algorithm> using namespace std; typedef long long LL; const int N = 200005; int order[N], c; LL a[N]; bool cmp(int i, int j) { return a[i] - a[j] > 1ll * c * (i - j); } int main() { int t; scanf("%d", &t); while (t--) { int n; scanf("%d%d", &n, &c); for (int i = 1; i <= n; i++) { scanf("%lld", &a[i]); order[i] = i; } sort(order + 2, order + n + 1, cmp); LL sum = a[1]; bool ok = true; for (int i = 2; i <= n; i++) { int idx = order[i]; if (sum + a[idx] >= 1ll * idx * c) { sum += a[idx]; } else { ok = false; break; } } printf("%s\n", ok ? "Yes" : "No"); } return 0; }
E1. Doremy's Drying Plan (Easy Version)
首先考虑暴力做法。
对于每一个位置
要计算
- 如果两者不交叉,
等于 和 中刚好覆盖一次的点的数量之和; - 如果两者交叉,假设两者交叉的线段为
,则 等于 和 中刚好覆盖一次的点的数量之和再加上 中恰好覆盖两次的点的数量。
如果直接枚举每一对线段,则时间复杂度为
- 对于两线段不交叉的情况,实际上直接从所有线段中挑出覆盖一次的点的数量最多的两条即可;
- 对于两线段交叉的情况,实际上真正有用的最多只有
对线段:对于每一个位置 ,如果它恰好被覆盖两次,此时就考虑覆盖这个点的两条线段即可。
对于某一个点,如何维护覆盖它的线段?这里我们可以把每个线段
参考代码
#include <cstdio> #include <vector> #include <set> #include <algorithm> using namespace std; const int N = 200005; vector<int> left[N], right[N]; int diff[N], cnt[N], sum1[N], sum2[N]; multiset<pair<int, int>> s; int main() { int t; scanf("%d", &t); while (t--) { int n, m, k; scanf("%d%d%d", &n, &m, &k); for (int i = 1; i <= n; i++) { left[i].clear(); right[i].clear(); diff[i] = 0; } for (int i = 1; i <= m; i++) { int l, r; scanf("%d%d", &l, &r); left[l].push_back(r); right[r].push_back(l); diff[l]++; diff[r + 1]--; } for (int i = 1; i <= n; i++) cnt[i] = cnt[i - 1] + diff[i]; for (int i = 1; i <= n; i++) { sum1[i] = sum1[i - 1]; sum2[i] = sum2[i - 1]; if (cnt[i] == 1) sum1[i]++; else if (cnt[i] == 2) sum2[i]++; } // only 1 int max1 = 0, max2 = 0; for (int i = 1; i <= n; i++) for (int x : left[i]) { // [i, x] int cur = sum1[x] - sum1[i - 1]; if (cur > max1) { max2 = max1; max1 = cur; } else if (cur > max2) max2 = cur; } int ans = max1 + max2; // 1+2 s.clear(); for (int i = 1; i <= n; i++) { for (int x : left[i]) s.insert({i, x}); if (cnt[i] == 2) { auto it = s.begin(); int l1 = it->first, r1 = it->second; it++; int l2 = it->first, r2 = it->second; int a = l1, b = r1, c = l2, d = r2; // sort if (a > b) swap(a, b); if (a > c) swap(a, c); if (a > d) swap(a, d); if (b > c) swap(b, c); if (b > d) swap(b, d); if (c > d) swap(c, d); ans = max(ans, sum1[d] - sum1[a - 1] + sum2[c] - sum2[b - 1]); } for (int x : right[i]) s.erase(s.find({x, i})); } for (int i = 1; i <= n; i++) if (cnt[i] == 0) ans++; printf("%d\n", ans); } return 0; }
E2. Doremy's Drying Plan (Hard Version)
考虑每个点
从左向右依次考虑每个点是否可以不被覆盖,那么在考虑
这里得到了一个子问题结构,考虑动态规划。设
如果覆盖在
容易发现当
假设有
将 Sparse Table 倒过来使用,用
对于每个端点
最终时间复杂度为
参考代码
#include <cstdio> #include <vector> #include <set> #include <algorithm> using namespace std; const int N = 200005; const int K = 15; const int LOG = 18; vector<int> left[N], right[N]; int diff[N], cnt[N], st[N][K][LOG], Log2[N]; // st[i][j][len] -> max(dp[i-(1<<len)+1],...,dp[i][j]) multiset<pair<int, int>> s; int query(int l, int r, int used) { int len = Log2[r - l + 1]; return max(st[r][used][len], st[l + (1 << len) - 1][used][len]); } int main() { Log2[1] = 0; for (int i = 2; i < N; i++) Log2[i] = Log2[i / 2] + 1; int t; scanf("%d", &t); while (t--) { int n, m, k; scanf("%d%d%d", &n, &m, &k); for (int i = 1; i <= n; i++) { left[i].clear(); right[i].clear(); diff[i] = 0; } for (int i = 1; i <= m; i++) { int l, r; scanf("%d%d", &l, &r); left[l].push_back(r); right[r].push_back(l); diff[l]++; diff[r + 1]--; } for (int i = 1; i <= n; i++) cnt[i] = cnt[i - 1] + diff[i]; for (int i = 1; i <= n; i++) for (int j = 0; j <= k; j++) for (int len = 0; len < LOG; len++) st[i][j][len] = 0; for (int i = 1; i <= n; i++) { for (int x : left[i]) s.insert({i, x}); for (int j = 0; j <= k; j++) { if (cnt[i] <= k) { int prej = j - cnt[i], pre = 0; for (auto p : s) { int l = p.first; // dp[i][j] = max(dp[...][j-cost]) + 1 if (l != pre && j >= cnt[i]) st[i][j][0] = max(st[i][j][0], query(pre, l - 1, prej) + 1); prej++; pre = l; } if (pre != i && j >= cnt[i]) st[i][j][0] = max(st[i][j][0], query(pre, i - 1, prej) + 1); } for (int len = 1; (1 << len) <= i + 1; len++) { int mid = i - (1 << (len - 1)); st[i][j][len] = max(st[i][j][len - 1], st[mid][j][len - 1]); } } for (int x : right[i]) s.erase(s.find({x, i})); } int ans = 0; for (int i = 0; i <= k; i++) ans = max(ans, query(0, n, i)); printf("%d\n", ans); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!