Loading

08.26

P6773

对于一个点 \(u\),我们关心通过它的最严的限制,即,下端点在 \(u\) 子树中的路径中,上端点的最大深度。

\(f_{u, i}\) 表示之,转移时先合并子树,再枚举这条边到父亲是否删除。

\[f_{u, i} \gets \sum_{j \leq dep_u} f_{u, i} f_{v, j} + \sum_{j \geq i} f_{u, i} f_{v, j} + f_{u, j} f_{v, i} - f_{u, i} f_{v, 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();
  }
} 
posted @ 2024-08-26 16:03  purplevine  阅读(6)  评论(0编辑  收藏  举报