Codeforces Round 936 (Div. 2)
基本情况
\(C\) 30min时候就想出很接近正解的做法了,但是没想清楚就草草否决后面改了一个很麻烦的做法,一小时才过。
\(D\) 原题,不屑于抄。
C. Tree Cutting
二分答案肯定不用说。
有一个直觉,要维护每个点的子树大小,然后把所有子树大小合适的点全部剪掉。
但直接比较子树大小和目标值是不可行的,因为删掉一个子树的子树会影响到其子树的大小。
其实要修改很简单,如果该子树合适直接剪掉,然后返回值改成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';
}
D. Birthday Gift
算复习一下原题吧。
H
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
通过这个参数与 \(w_i\) 进行按位与,显然能自动检测符合题解思路条件的物品,全部加上即可。
本题
那就没啥好说了,几乎是一致的,只是 \(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';
}
E. Girl Permutation
看起来就很组合数,而且有一组大样例,直接对着找规律。
input:
20 5 4
1 2 3 4 12
12 13 18 20
output:
317580808
首先毋庸置疑最大值在 \(12\) 这个位置
其次,\([5, 11]\) 这个区间的数字一定不能大于 \(a_4\),因为如果大于就也会被算成前缀最大。
同理,\([14, 17],[19,19]\) 这几个区间的数字一定不能大于 \(a_{18}, a_{20}\)。
\(12\) 的位置上是 \(20\) 已经确定了,分析剩下的 \(19\) 个数字。
- 先分析左边
- 左边先给 \(11\) 个数字,\(\binom{19}{11}\)。
- 然后 \(a_4\) 上的数字肯定是 \(11\) 个数字里面最大的,现在剩 \(10\) 个数字。
- 在剩下的 \(10\) 个数字中选 \(7\) 个排入 \([5, 11]\),任意顺序,\(A_{10}^{7}\)。
- 然后还剩下三个数字,按顺序放入,只有一种方案。
- 再分析右边
- 同理 \(A_{6}^{4}\)
\[C_{19}^{11}A_{10}^7A_{6}^{4} = 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';
}