Codeforces Round 932 (Div. 2)
写在前面
比赛地址:https://codeforces.com/contest/1935。
被精心构造的 C 的样例鲨了的一集。
妈的天使骚骚☆REBOOT完全就是他妈拔作啊我草,要是被人知道我他妈推了全线要被笑话一辈子吧、、、
A
签到。
操作偶数次,则答案仅可能为 s
或 reverse(s) + s
,更长的字符串的前缀一定为二者之一,则选择这两者是最优的。
则仅需比较 s
与 reverse(s)
的字典序即可。
复制复制// /* 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; std::string t = s; std::reverse(t.begin(), t.end()); if (s <= t) std::cout << s << "\n"; else std::cout << t + s << "\n"; } return 0; }
B
枚举。
显然答案仅可能为 或无解。
于是检查 是否可以成为答案。发现若可以分成多段,则一定可以分成两段,于是仅需枚举两段互补的前缀后缀并检查它们的 是否为 即可。
赛时写的比较傻比,先找到了最短的满足 的前缀,然后再检查剩下的后缀是否也满足条件。
//枚举 /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e5 + 10; //============================================================= int n, ans, a[kN]; bool yes[kN]; std::vector <std::pair <int, int> > sol; int cntnum, nowtime, cnt[kN], tim[kN]; //============================================================= void add(int x_) { if (tim[x_] != nowtime) cnt[x_] = 0, tim[x_] = nowtime; if (!cnt[x_]) ++ cntnum; ++ cnt[x_]; } void clear() { cntnum = 0; ++ nowtime; } void check(int mex_) { int sum = 0, last = 1; sol.clear(); for (int i = 1; i <= n; ++ i) { if (a[i] < mex_) add(a[i]); if (cntnum == mex_) { ++ sum; clear(); if (sol.empty()) sol.push_back(std::make_pair(last, i)), last = i + 1; } } sol.push_back(std::make_pair(last, n)); if (sum >= 2) ans = mex_; } //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); int T; std::cin >> T; while (T --) { ans = -1; sol.clear(); clear(); std::cin >> n; for (int i = 0; i <= n; ++ i) yes[i] = 0; for (int i = 1; i <= n; ++ i) { std::cin >> a[i]; yes[a[i]] = 1; } for (int i = 0; i <= n; ++ i) { if (!yes[i]) { check(i); break; } } if (ans == -1) { std::cout << -1 << "\n"; continue; } std::cout << sol.size() << "\n"; for (auto x: sol) std::cout << x.first << " " << x.second << "\n"; } return 0; }
C
枚举,排序,线段树
被精心构造的 C 的样例鲨了的一集。
这个贡献的形式显然是典中典,考虑将所有元素按 排序然后枚举区间 表示钦定选择了元素 ,使选择的元素满足后半部分贡献为 ,之后应在 中再选择尽可能多的元素使总贡献不超过 ,此时仅需考虑 的贡献即可。
显然应当按照 升序选择元素,一个显然的想法是用线段树维护 中元素的属性 ,以 的值为下标维护区间元素个数与区间贡献之和,然后在线段树上以上限 二分即可得到至多能选择元素的数量。
在枚举区间的同时维护线段树即可。注意对 先离散化,总时间复杂度 级别。
为什么想到这个?因为前天刚写了一个线段树上二分的题,直接复制过来用了哈哈
有 的 DP 解法。
赛时线段树:
//枚举,排序,线段树 /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long #define pii std::pair<int,int> #define mp std::make_pair const int kN = 2010; //============================================================= int n, datanum, b[kN]; pii a[kN]; LL ans, l; //============================================================= namespace Seg { #define ls (now_<<1) #define rs (now_<<1|1) #define mid ((L_+R_)>>1) const int kNode = kN << 2; LL sum[kNode], cnt[kNode]; void Pushup(int now_) { sum[now_] = sum[ls] + sum[rs]; cnt[now_] = cnt[ls] + cnt[rs]; } void Insert(int now_, int L_, int R_, int pos_, LL sum_, LL cnt_) { if (L_ == R_) { sum[now_] += sum_; cnt[now_] += cnt_; return ; } if (pos_ <= mid) Insert(ls, L_, mid, pos_, sum_, cnt_); else Insert(rs, mid + 1, R_, pos_, sum_, cnt_); Pushup(now_); } LL Query(int now_, int L_, int R_, LL lim_) { if (L_ == R_) { return std::min(cnt[now_], lim_ / b[L_]); } LL sl = sum[ls], cl = cnt[ls]; if (lim_ <= sl) return Query(ls, L_, mid, lim_); return cl + Query(rs, mid + 1, R_, lim_ - sl); } #undef ls #undef rs #undef mid } void Solve() { ans = 0; for (int i = 1; i <= n; ++ i) { for (int j = i; j <= n; ++ j) { LL cost = 1ll * b[a[i].second] + 1ll * (j > i) * b[a[j].second] + a[j].first - a[i].first; if (l >= cost) { LL c = 1 + (j > i) + Seg::Query(1, 1, datanum, l - cost); ans = std::max(ans, c); } if (j > i) Seg::Insert(1, 1, datanum, a[j].second, b[a[j].second], 1); } for (int j = i; j <= n; ++ j) { if (j > i) Seg::Insert(1, 1, datanum, a[j].second, -b[a[j].second], -1); } } } void Init() { ans = 0; std::cin >> n >> l; for (int i = 1; i <= n; ++ i) { int x, y; std::cin >> x >> y; a[i] = mp(y, x); } std::sort(a + 1, a + n + 1); for (int i = 1; i <= n; ++ i) b[i] = a[i].second; std::sort(b + 1, b + n + 1); datanum = std::unique(b + 1, b + n + 1) - b - 1; for (int i = 1; i <= n; ++ i) { a[i].second = std::lower_bound(b + 1, b + datanum + 1, a[i].second) - b; } } //============================================================= 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(); Solve(); std::cout << ans << "\n"; } return 0; } /* 1 5 100 1 1 1000 2 1000 3 1000 4 1 98 */
D
数学,容斥
一眼题。C 实在调不出来看了一眼就会的呃呃题,五分钟过了之后继续调 C 呃呃
考虑求补集,即求多少二元组 满足 。保证了 两两不同,则简单容斥下即求下列三部分二元组的数量:
- ,数量为 。
- ,数量为 。
- ,若二元组满足该条件,则有 且 ,则记 的数量分别为 ,二元组数量为 。
答案即为 ,复杂度 级别。
//数学,容斥 /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 3e5 + 10; //============================================================= int n, c, s[kN]; LL ans; //============================================================= void Solve() { ans = 1ll * (c + 1) * (c + 2) / 2ll; int cnt[2] = {0, 0}; for (int i = 1; i <= n; ++ i) { ans -= s[i] / 2ll + 1; ans -= c - s[i] + 1; ++ cnt[s[i] % 2]; } ans += 1ll * cnt[0] * (cnt[0] + 1ll) / 2ll; ans += 1ll * cnt[1] * (cnt[1] + 1ll) / 2ll; } //============================================================= 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 >> c; for (int i = 1; i <= n; ++ i) std::cin >> s[i]; Solve(); std::cout << ans << "\n"; } return 0; }
E
位运算,贪心,线段树
好玩的按位或。
先考虑 的情况,考虑按位贪心。设当前降序枚举到 位,记 表示 内有多少元素的 此位为 1:
- :则此位无贡献。
- :贡献为 ,来自唯一的此位为 1 的数。
- :贡献为 ,令 此位为 1 的两个元素一个取 ,一个取 即可,则之后所有位均有贡献,直接停止枚举即可。
当 时类似,同样考虑按位贪心并考虑每位为 1 的有多少个数。 的情况不受影响,然而对于 可能有 ,使对应元素无法取到 影响 的贡献。但想法还是相同的:对于 的位,仅取其中一个保留第 位贡献,其他数向下调整使得某位 之后的所有位均有贡献。
手玩下发现满足条件的最大的 即为 中按照降序第一位不同的,由于 则一定有 第 位为 1, 第 位为 0,则此时该元素可取到 ,使 位之后的贡献均为 1——感觉和特殊情况很类似?
于是考虑先预处理出每个元素的 中按照降序第一位不同的位 ,发现每个元素 位之前的部分一定可以贡献到答案中,于是先将这部分提取出来线段树维护区间按位或;对于 位及之后的部分 ,此时相当于 的对 无限制的情况,可以套用一开始的特殊情况,前缀和维护区间内有多少元素某位为 1 即可。
注意特判 的情况,贡献恒为 直接用线段树维护即可。
总时间复杂度 级别。
另外注意:取出 的第 位时尽量使用 v >> i & 1
而非 1 << i & v
,后者不知道为什么会挂掉。
//位运算,贪心,线段树 /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; //============================================================= int n, m, must[kN], cnt[31][kN]; //============================================================= namespace Seg { #define ls (now_<<1) #define rs (now_<<1|1) #define mid ((L_+R_)>>1) const int kNode = kN << 2; int t[kNode]; void Pushup(int now_) { t[now_] = t[ls] | t[rs]; } void Build(int now_, int L_, int R_) { if (L_ == R_) { t[now_] = must[L_]; return ; } Build(ls, L_, mid), Build(rs, mid + 1, R_); Pushup(now_); } int Query(int now_, int L_, int R_, int l_, int r_) { if (l_ <= L_ && R_ <= r_) return t[now_]; int ret = 0; if (l_ <= mid) ret |= Query(ls, L_, mid, l_, r_); if (r_ > mid) ret |= Query(rs, mid + 1, R_, l_, r_); return ret; } #undef ls #undef rs #undef mid } void Init() { std::cin >> n; for (int i = 1; i <= n; ++ i) { int x, y; std::cin >> x >> y; for (int j = 29; j >= 0; -- j) { cnt[j][i] = cnt[j][i - 1]; } if (x == y) { must[i] = x; } else { int val = (1 << ((int) log2(x ^ y) + 1)) - 1; must[i] = y - (y & val); val = y & val; for (int j = 29; j >= 0; -- j) { cnt[j][i] += ((val >> j & 1) > 0); } } } Seg::Build(1, 1, n); } int Query(int l_, int r_) { int ans = Seg::Query(1, 1, n, l_, r_); for (int i = 29; i >= 0; -- i) { int c = cnt[i][r_] - cnt[i][l_ - 1] + (ans >> i & 1); if (c > 1) { ans |= (1 << (i + 1)) - 1; } else if (c == 1) { ans |= (1 << i); } } return ans; } //============================================================= 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(); std::cin >> m; while (m --) { int l_, r_; std::cin >> l_ >> r_; std::cout << Query(l_, r_) << " "; } std::cout << "\n"; } return 0; } /* 1 1 2 2 1 1 1 */
写在最后
学到了什么:
- B:区间 全局 。
- E:提取贡献,转化为特殊情况。取出 的第 位时尽量使用
v >> i & 1
而非1 << i & v
,后者不知道为什么会挂掉。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现