Codeforces Round 936 (Div. 2)

1|0基本情况

C 30min时候就想出很接近正解的做法了,但是没想清楚就草草否决后面改了一个很麻烦的做法,一小时才过。

D 原题,不屑于抄。

2|0C. Tree Cutting

Problem - C - Codeforces

二分答案肯定不用说。

有一个直觉,要维护每个点的子树大小,然后把所有子树大小合适的点全部剪掉。

但直接比较子树大小和目标值是不可行的,因为删掉一个子树的子树会影响到其子树的大小。

其实要修改很简单,如果该子树合适直接剪掉,然后返回值改成0即可。

void solve() { int n, k; std::cin >> n >> k; std::vector adj(n, std::vector<int>()); std::vector<int> dp(n, 1); for (int i = 0, u, v; i < n - 1; ++i) { std::cin >> u >> v; --u, --v; adj[u].push_back(v); adj[v].push_back(u); } int lo(1), hi(n); while(lo <= hi) { int mid(lo + hi >> 1); int cnt(0); auto dfs = [&](auto self, int x, int fa) -> int { int siz(1); for (auto& to : adj[x]) if (to != fa) { siz += self(self, to, x); } if (siz >= mid) {//符合条件直接剪掉就好了 cnt++; return 0;//剪掉后这个子树大小就是0 } return siz; }; dfs(dfs, 0, -1); if (cnt > k) { lo = mid + 1; } else { hi = mid - 1; } } std::cout << lo - 1 << '\n'; }

然而当时不细想就草草否决,改成了自觉的“稳妥”的离线做法,多浪费了半小时。

void solve() { int n, k; std::cin >> n >> k; std::vector adj(n, std::vector<int>()); std::vector<int> t, f(n), pos(n); for (int i = 0, u, v; i < n - 1; ++i) { std::cin >> u >> v; --u, --v; adj[u].push_back(v); adj[v].push_back(u); } auto dfs = [&](auto self, int x, int fa) -> void { t.push_back(x); pos[x] = sz(t); for (auto& to : adj[x]) if (to != fa) { self(self, to, x); } return ; }; dfs(dfs, 0, -1); int lo(0), hi(n); auto check = [&](auto& mid) -> bool { int cnt = 0; for (int i = n - 1; i >= 0; i--) { int u = t[i]; f[u] = 1; for (auto to : adj[u]) if (pos[to] >= pos[u]) { f[u] += f[to]; } if (f[u] >= mid) { f[u] = 0;//典型的脱裤子放屁 cnt++; } } return cnt > k; }; while (lo <= hi) { int mid(lo + hi >> 1); if (check(mid)) { lo = mid + 1; } else { hi = mid - 1; } } std::cout << lo - 1 << '\n'; }

3|0D. Birthday Gift

Problem - D - Codeforces

算复习一下原题吧。

3|1H

https://ac.nowcoder.com/acm/contest/67741/H

位运算好题

  • 先考虑每个物品的重量都只含 1bit​ 的情况,可以帮助对这题要解决什么问题有个大致了解
  • 记所选物品重量或起来是 𝑐枚举 𝑚 里是 1 的某个 bit,强制 𝑐 里该位为 0,则该位将 𝑚 分成了前后两部分
    • 对于前面的那部分位(更高位),要求所选的物品这些位必须是 𝑚 的子集(即 𝑚 对应位是 1 才能选)
    • 对于后面的那部分位(更低位),没有任何限制
  • 因此,枚举 𝑚 里每一位作为这个分界,每个物品就变成了要么能选要么不能选、彼此之间也不影响,所以把能选的都选上就好,最后再特判一下 c=m 的状况,即可保证枚举了所有情况
void solve() { int n, m; cin >> n >> m; vector<int> v(n), w(n); for (int i = 0; i < n; i++) cin >> v[i] >> w[i]; ll ans = 0; auto get = [&](int s) { ll res = 0; for (int i = 0; i < n; i++) { if ((s & w[i]) == w[i]) { res += v[i]; } } ans = max(ans, res); }; for (int i = 29; i >= 0; i--) { if (m >> i & 1) { get((m ^ (1 << i)) | ((1 << i) - 1));//很关键 } } get(m);//处理C=M的情况 cout << ans << endl; }

get函数内的传参用了几个 trick.

  • m ^ (1 << i)//把 m 的第 i 位取反,这里是置为 0
  • (m ^ (1 << i) | ((1 << i) - 1))//把 i 位之前(右边)的位数全部变成 1,因为由题解,更低位不限制 //1 << i 是第 i 位为 1,右边都是 0 //1 << i - 1 则会把第 i 位变成 0, 右边全部变成 1

通过这个参数与 wi 进行按位与,显然能自动检测符合题解思路条件的物品,全部加上即可。

3|2本题

那就没啥好说了,几乎是一致的,只是 get 的时候改成题目要求的连续异或和,符合条件的异或和加入背包中,更新最大值就行了。

void solve() { int n, m; std::cin >> n >> m; std::vector<int> a(n); for (auto& x : a) std::cin >> x; int xorSum(0); for (auto& x : a) xorSum ^= x; if (xorSum > m) {error ; return ;} i64 ans(0); auto get = [&](int mask) { i64 res(0), cnt(0); for (auto& x : a) { res ^= x; if ((res & mask) == res) { cnt++; res = 0; } } if (res != 0) {//因为他是要求连续的整段都要合法,如果剩下的最后一段不能并入mask,那就不合法 cnt = 0; } ans = std::max(ans, cnt); }; for (int i = 0; i < 31; i++) { if (m >> i & 1) { get((m ^ (1 << i)) | ((1 << i) - 1)); } } get(m); std::cout << ans << '\n'; }

4|0E. Girl Permutation

Problem - E - Codeforces

看起来就很组合数,而且有一组大样例,直接对着找规律。

input: 20 5 4 1 2 3 4 12 12 13 18 20 output: 317580808

首先毋庸置疑最大值在 12 这个位置

其次,[5,11] 这个区间的数字一定不能大于 a4,因为如果大于就也会被算成前缀最大。

同理,[14,17],[19,19] 这几个区间的数字一定不能大于 a18,a20

12 的位置上是 20 已经确定了,分析剩下的 19​ 个数字。

  • 先分析左边
    • 左边先给 11 个数字,(1911)
    • 然后 a4 上的数字肯定是 11 个数字里面最大的,现在剩 10 个数字。
    • 在剩下的 10 个数字中选 7 个排入 [5,11],任意顺序,A107
    • 然后还剩下三个数字,按顺序放入,只有一种方案。
  • 再分析右边
    • 同理 A64

C1911A107A64=317580808

然后代码实现就好了。

void solve() { i64 x, y; int n; std::cin >> n >> x >> y; std::vector<int> a(x + 1), b(y + 1); for (int i = 1; i <= x; i++) std::cin >> a[i]; for (int i = 1; i <= y; i++) std::cin >> b[i]; if (a[x] != b[1] or a[1] != 1 or b[y] != n) {std::cout << 0 << '\n'; return ;} int remain(a[x] - 1);//先把中间最大的确定下来,减一 Z ans(1); int last(a[x]); for (int i = x - 1; i >= 1; i--) {//左边先来一遍 remain -= 1;//先把最大的确定下来,减一 int num(last - a[i] - 1);//中间有几个自由数字 ans *= comb.A(remain, num); remain -= num;//减去这些自由数字 last = a[i]; } remain = n - a[x]; last = a[x]; for (int i = 2; i <= y; i++) {//右边再来一遍 remain -= 1; int num(b[i] - last - 1); ans *= comb.A(remain, num); remain -= num; last = b[i]; } ans *= comb.binom(n - 1, a[x] - 1); std::cout << ans << '\n'; }

__EOF__

本文作者Kdlyh
本文链接https://www.cnblogs.com/kdlyh/p/18091261.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   加固文明幻景  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示