08.26
P6773
对于一个点 \(u\),我们关心通过它的最严的限制,即,下端点在 \(u\) 子树中的路径中,上端点的最大深度。
\(f_{u, i}\) 表示之,转移时先合并子树,再枚举这条边到父亲是否删除。
线段树合并维护之,合并时维护左右侧的和,那么需要维护区间乘法标记与加法标记,感觉难写。
P3750
容易发现 \(n\) 个按钮线性无关,因此确定完初始状态后,按按钮的方案是唯一的,且顺序不重要。
容易根据现有的灯的明灭情况确定最少按按钮的次数。做 dp:\(f_i\) 表示剩 \(i\) 步的按按钮期望次数。有 \(f_i = \frac{i}{n} f_{i-1} + \frac{n-i}{n} f_{i+1}+1\)。求差分数组,\(\frac{i}{n}(f_i-f_{i-1}) = \frac{n-i}{n} (f_{i+1}-f_i)+1\),求完后累加 \(> k\) 的位置即可。
初始 \(2^n\) 种情况与 \(2^n\) 种按键方案一一对应,于是可以想到按键方案不重要,结合可以求出步数可以想到只有步数是重要的。然而我想不到。
#include <bits/stdc++.h>
const int mod = 100003;
int qpow(int a, int b) {
int ans(1);
for (; b; b >>= 1) {
if (b & 1) ans = 1ll * ans * a % mod;
a = 1ll * a * a % mod;
}
return ans;
}
int main() {
int n, k; scanf("%d %d", &n, &k);
std::vector<std::vector<int>> fac(n + 1);
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j += i) {
fac[j].push_back(i);
}
}
std::vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int cnt = 0;
for (int i = n; i >= 1; i--) if (a[i]) {
for (auto j : fac[i]) a[j] ^= 1;
++cnt;
}
int ans = 0;
if (cnt <= k) ans = cnt;
else {
std::vector<int> f(n + 2);
for (int i = n; i >= 1; i--) {
f[i] = (1ll * (n - i) * f[i + 1] % mod + n) % mod;
f[i] = 1ll * f[i] * qpow(i, mod - 2) % mod;
}
for (int i = k + 1; i <= cnt; i++)
(ans += f[i]) %= mod;
(ans += k) %= mod;
}
for (int i = 1; i <= n; i++)
ans = 1ll * ans * i % mod;
printf("%d\n", ans);
}
P9133
拆贡献。对于一个 \(x\),所有其子树内的对方棋子带来 \(+1\) 的贡献,其祖先中的对方棋子带来 \(-1\) 的贡献。忽视掉对方这个条件,因为一对己方棋子的贡献会互相抵消,得到贡献为 \(siz_x-dep_x\)。
据此排序贪心取即可,还要算上购买的花费。
#include <bits/stdc++.h>
int main() {
int n; scanf("%d", &n);
std::vector<int> w(n);
for (int &x : w) scanf("%d", &x);
std::vector<std::vector<int>> e(n);
for (int i = 1, f; i < n; i++) {
scanf("%d", &f), --f;
e[f].push_back(i);
}
std::vector<int> siz(n), dep(n);
std::function<void(int)> dfs = [&](int u) {
siz[u] = 1;
for (auto v : e[u]) {
dep[v] = dep[u] + 1, dfs(v), siz[u] += siz[v];
}
};
dep[0] = 1; dfs(0);
std::vector<int> b(n);
for (int i = 0; i < n; i++)
b[i] = siz[i] - dep[i] - w[i];
std::sort(b.begin(), b.end(), std::greater<int>());
long long ans(0);
for (int i = 0; i < n; i += 2)
ans += b[i];
printf("%lld\n", ans);
}
CF1451F
考虑左下-右上的斜线,每条路径至多经过每条斜线一次。
考虑 nim 游戏,当所有斜线上异或和均为 \(0\) 时任意操作无法保持该性质,且任意非全 \(0\) 场面均可一步到达该场面,开局判斜线异或和是否全为 \(0\) 即可。
#include <bits/stdc++.h>
void solve() {
int n, m; scanf("%d %d", &n, &m);
std::vector<int> f(n + m);
for (int i = 0; i < n; i++)
for (int j = 0, x; j < m; j++)
scanf("%d", &x), f[i + j] ^= x;
if (std::accumulate(f.begin(), f.end(), 0) == 0)
printf("Jeel\n");
else printf("Ashish\n");
}
int main() {
int T; scanf("%d", &T); while (T--) {
solve();
}
}
CF1149E
贯彻上一题的想法,试图找到必胜态与必败态。还是可以尝试给点分组,所有组的点权异或和均为零时,操作点所在组一定不能继续为零;且任意局面都可以到达所有组点权异或和均为零的场面。
保证对于任意 \(x < y\),组别为 \(y\) 的任意点都可以到达至少一个组别为 \(x\) 的点即可。那就是让 \(y\) 的组别为所有其后缀的组别的 \(\text{mex}\),容易发现这是唯一合法的构造。
#include <bits/stdc++.h>
int main() {
int n, m; scanf("%d %d", &n, &m);
std::vector<std::vector<int>> e(n), pre(n);
std::vector<int> a(n), deg(n), sg(n), sum(n);
for (int &x : a) scanf("%d", &x);
for (int i = 0, u, v; i < m; i++) {
scanf("%d %d", &u, &v), --u, --v;
e[u].push_back(v), ++deg[u];
pre[v].push_back(u);
}
auto d(deg);
std::queue<int> q;
for (int i = 0; i < n; i++) if (!deg[i])
q.push(i);
std::vector<std::vector<int>> b(n);
while (q.size()) {
int u = q.front(); q.pop();
std::vector<int> vis(deg[u] + 1);
for (auto v : e[u]) if (sg[v] <= deg[u])
vis[sg[v]] = 1;
while (vis[sg[u]]) ++sg[u];
b[sg[u]].push_back(u);
for (auto v : pre[u]) if (!--d[v]) q.push(v);
}
for (int i = 0; i < n; i++)
sum[sg[i]] ^= a[i];
int pl = -1;
for (int i = 0; i < n; i++) if (sum[i])
pl = i;
if (pl == -1) return printf("LOSE\n"), 0;
for (int u : b[pl]) if ((a[u] ^ sum[pl]) <= a[u]) {
a[u] ^= sum[sg[u]];
for (auto v : e[u]) {
a[v] ^= sum[sg[v]], sum[sg[v]] = 0;
}
printf("WIN\n");
for (auto x : a) printf("%d ", x);
printf("\n");
return 0;
}
}
P3175
min-max 容斥入门。
设 \(a_i\) 为一个随机变量,表示第 \(i\) 位变为 \(1\) 的时间。根据 min-max 容斥,求 \(\sum_{T} (-1)^{|T| + 1} \min(T)\) 即可。
考虑 \(\min(T)\) 怎么求,这是随意操作到 \(T\) 中任意一位为 \(1\) 的概率,求随意操作一次,含有 \(T\) 中的数位的概率。做一个高维前缀和即可。
#include <bits/stdc++.h>
const double eps = 1e-11;
int main() {
int n; scanf("%d", &n);
std::vector<double> p(1 << n);
for (double &x : p) scanf("%lf", &x);
for (int i = 0; i < n; i++)
for (int j = 0; j < (1 << n); j++) if ((j & (1 << i)))
p[j] += p[j ^ (1 << i)];
int all = (1 << n) - 1;
double ans = 0;
for (int i = 1; i < (1 << n); i++) {
if (1 - p[all ^ i] < eps) return printf("INF\n"), 0;
if (__builtin_popcount(i) & 1) ans += 1 / (1 - p[all ^ i]);
else ans -= 1 / (1 - p[all ^ i]);
}
printf("%.7lf\n", ans);
}
P5644
经典转化是允许空枪,即,猎人死后仍然可以被击中,这样猎人死亡并不影响每个猎人被击中的概率。
钦定最先是好做的,钦定最后是难做的。取一个集合 \(T\),设其权值和为 \(w_T\),尝试计算 \(T\) 中所有猎人都在 \(1\) 之后被射杀的概率,为 \(\dfrac{w_1}{w_1+w_T}\),即 \(1\) 是这些猎人中最先被射杀的概率。于是答案为 \(\sum_T (-1)^{|T|} \dfrac{w_1}{w_1+w_T}\)。
值域不大,那么求满足 \(w_T=w\) 的 \(\sum (-1)^{|T|}\) 即可,相当于 \([x^w] \prod (1 - x^{w_i})\),分治乘即可。
int main() {
int n; scanf("%d", &n);
std::vector<int> a(n);
int sum(0);
for (int &x : a) scanf("%d", &x), sum += x;
std::vector<Z> inv(sum + 1);
inv[1] = 1;
for (int i = 2; i <= sum; i++)
inv[i] = (P - P / i) * inv[P % i];
std::function<Poly(int, int)> calc = [&](int l, int r) {
if (l == r) {
Poly x(a[l] + 1);
x[0] = 1, x[a[l]] = P - 1;
return x;
}
int mid = l + r >> 1;
return calc(l, mid) * calc(mid + 1, r);
} ;
Poly x = calc(1, n - 1);
int a0 = a[0];
Z ans(0);
for (int i = 0; i <= sum - a0; i++) {
ans += x[i] * a0 * inv[a0 + i];
}
printf("%d\n", ans);
}
P4221
设划分完集合 \(S\) 元素的最小值为 \(f_S\),有转移 \(f_S \cdot g_T \cdot w_T^p \to (|S|+|T|)^p f_{S \cup T} (S \cap T = \varnothing)\),其中 \(g_T\) 为 \(T\) 集合是否合法。将 \(w_T^p\) 合入 \(g_T\),要求一个在线子集卷积,按照 \(|S|\) 的顺序转移即可。
#include <bits/stdc++.h>
const int mod = 998244353;
int qpow(int a, int b) {
int ans(1);
for (; b; b >>= 1) {
if (b & 1) ans = 1ll * ans * a % mod;
a = 1ll * a * a % mod;
}
return ans;
}
std::vector<int> fa;
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void addn(int &x, int y) { if ((x += y) >= mod) x -= mod; }
std::vector<int> fwt(std::vector<int> f, int t) {
int n = f.size();
for (int l = 1; l < n; l <<= 1) {
for (int p = 0; p < n; p += l + l) {
for (int i = p; i < p + l; i++)
if (t) addn(f[i + l], f[i]);
else addn(f[i + l], mod - f[i]);
}
}
return f;
}
int main() {
int n, m, p; scanf("%d %d %d", &n, &m, &p);
std::vector<int> inv(1 << n);
auto pow = [&](int x) -> int {
if (!p) return 1;
if (p == 1) return x;
return 1ll * x * x % mod;
};
std::vector<std::vector<int>> e(n, std::vector<int>(n));
for (int i = 0, u, v; i < m; i++) {
scanf("%d %d", &u, &v), --u, --v;
e[u][v] = e[v][u] = 1;
}
std::vector<int> w(n);
for (int &x : w) scanf("%d", &x);
fa.resize(n);
std::vector<std::vector<int>> g(n + 1, std::vector<int>(1 << n)), f(g);
for (int S = 0; S < (1 << n); ++S) {
std::vector<int> a, deg(n);
std::iota(fa.begin(), fa.end(), 0);
for (int i = 0; i < n; i++) if (S & (1 << i))
a.push_back(i);
for (auto u : a)
for (auto v : a) if (e[u][v])
++deg[u], fa[find(u)] = find(v);
int cnt = 0, lst = -1; bool f = 1;
for (auto i : a) {
f &= (lst == -1 || find(i) == lst);
lst = find(i);
}
int sum(0);
for (auto u : a) sum += w[u];
for (int i = 0; i < n; i++) f &= (deg[i] & 1) == 0;
if (!f)
g[__builtin_popcount(S)][S] = pow(sum);
inv[S] = pow(qpow(sum, mod - 2));
if (sum) assert(1ll * qpow(sum, mod - 2) * sum % mod == 1);
}
for (int i = 0; i <= n; i++)
g[i] = fwt(g[i], 1);
f[0][0] = 1;
f[0] = fwt(f[0], 1);
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++)
for (int k = 0; k < (1 << n); k++)
(f[i][k] += 1ll * f[j][k] * g[i - j][k] % mod) %= mod;
f[i] = fwt(f[i], 0);
for (int k = 0; k < (1 << n); k++) {
if (__builtin_popcount(k) == i) f[i][k] = 1ll * f[i][k] * inv[k] % mod;
else f[i][k] = 0;
}
if (i != n) f[i] = fwt(f[i], 1);
}
printf("%d\n", f[n][(1 << n) - 1]);
}
CF1896G
分成 \(n\) 组,首先在每组中求最大值,把所有最大值拿出来,再求一次得到的就是全局最大值。
我们考虑从大到小一个个求值。如果当前最大值来自于第 \(i\) 组,将它去掉。如果一个组内数值少于 \(n\) 个,就从其它组非最大值中借数字来用。如果最大值不出自这一组,就把最大值移到该组。
这样的次数是 \(2(n^2 - n + 1)-1\),需要再省掉 \(n\) 次。那么考虑还剩下 \(2n-1\) 个数的局面,这个时候最小的 \(n-1\) 个数值是知道的,每次只用一次操作就可以求出当前最大值了。于是刚好能过。
#include <bits/stdc++.h>
void solve() {
auto qry = [&](std::set<int> s) {
printf("? ");
for (auto x : s) printf("%d ", x + 1);
printf("\n"), fflush(stdout);
int x; scanf("%d", &x), --x;
return x;
};
int n; scanf("%d", &n);
std::vector<std::set<int>> s(n);
std::vector<int> vis(n * n), mx(n), rnk(n * n, -1);
for (int i = 0; i < n; i++) {
for (int j = n * i; j < n * (i + 1); j++)
s[i].insert(j);
mx[i] = qry(s[i]), vis[mx[i]] = 1;
}
auto expand = [&](std::set<int> p) {
auto t = p;
for (int i = 0; i < n * n && (int)t.size() < n; i++) if (!vis[i] && !t.count(i)) {
t.insert(i);
}
return t;
};
int cnt = 0;
for (int _ = n * n; _ >= 2 * n; _--) {
std::set<int> t;
for (int i = 0; i < n; i++) t.insert(mx[i]);
int x = qry(t);
rnk[x] = cnt++;
for (int i = 0; i < n; i++) if (s[i].count(x)) {
s[i].erase(x);
int y = qry(expand(s[i]));
if (!s[i].count(y))
for (int j = 0; j < n; j++) if (s[j].count(y))
s[j].erase(y);
s[i].insert(y), mx[i] = y, vis[y] = 1;
break;
}
}
std::set<int> t1, t2;
for (int i = 0; i < n; i++)
t1.insert(mx[i]);
for (int i = 0; i < n * n; i++) if (!vis[i])
t2.insert(i);
std::vector<int> t3;
for (auto x : t2) t3.push_back(x);
t2 = t1;
for (int i = 1; i < n; i++) {
int x = qry(t1);
rnk[x] = cnt++, t1.erase(x), t2.erase(x);
if (i < n - 1) t1.insert(t3[i]);
}
rnk[*t2.begin()] = cnt++;
std::vector<int> val(cnt);
for (int i = 0; i < n * n; i++) if (rnk[i] != -1)
val[rnk[i]] = i;
printf("! ");
for (auto x : val) printf("%d ", x + 1);
printf("\n");
fflush(stdout);
}
int main() {
int T; scanf("%d", &T); while (T--) {
solve();
}
}
本文来自博客园,作者:purplevine,转载请注明原文链接:https://www.cnblogs.com/purplevine/p/18381152