ZJOI 2019 Day 1 简要题解

mahjong

期望和牌巡数等于对于所有 i ≥ 0 i\ge 0 i0 ,摸了 i i i 张牌仍然没和的概率之和。考虑如何DP一堆牌是否能和,七对子可以直接把对子记下来,而普通的集合需要跑一个DP,记 f ( i , a , b , c ) f(i, a,b,c) f(i,a,b,c) 表示考虑前 i i i 张, a a a 表示是否有雀头, b b b 表示 i − 2 , i − 1 , i i-2,i-1,i i2,i1,i 的顺子个数, c c c 表示 i − 1 , i , i + 1 i-1,i,i+1 i1,i,i+1 的顺子个数,能得到的最大面子数,这里 0 ≤ b , c &lt; 3 , a ∈ { 0 , 1 } 0\le b,c&lt;3, a\in \{0, 1\} 0b,c<3,a{0,1} 。然后就可以跑个DP套DP了。注意到有效状态不多,直接搜出所有有效状态就能过了。

#include <bits/stdc++.h>

using namespace std;

const int md = 998244353;

inline void add(int &x, int y) {
  x += y;
  if (x >= md) {
    x -= md;
  }
}

inline int mul(int x, int y) {
  return (int) ((long long) x * y % md);
}

long long encode(vector<int> dp) {
  long long code = 0;
  for (int i = 0; i < 19; ++i) {
    code = code * 8 + dp[i] + 1;
  }
  return code;
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  vector<vector<int>> goes;
  vector<vector<int>> all;
  map<long long, int> id;
  auto extend = [&](vector<int> dp) {
    long long code = encode(dp);
    if (!id.count(code)) {
      id[code] = all.size();
      all.push_back(dp);
      goes.push_back(vector<int>(5, -1));
    }
  };
  {
    vector<int> initial(19, -1);
    initial[0] = initial[18] = 0;
    extend(initial);
  }
  for (int i = 0; i < (int) all.size(); ++i) {
    vector<int> dp = all[i];
    for (int num = 0; num <= 4; ++num) {
      vector<int> new_dp(19, -1);
      new_dp[18] = dp[18] + (num >= 2);
      for (int state = 0; state < 18; ++state) {
        if (dp[state] != -1) {
          int eyes = state / 9, foo = state / 3 % 3, bar = state % 3;
          if (num >= foo + bar) {
            int new_num = num - (foo + bar);
            for (int baz = 0; baz < 3 && baz <= new_num; ++baz) {
              int to_triplet = new_num - baz;
              new_dp[eyes * 9 + bar * 3 + baz] = max(new_dp[eyes * 9 + bar * 3 + baz], dp[state] + foo + (to_triplet >= 3));
              if (to_triplet >= 2) {
                new_dp[(eyes | 1) * 9 + bar * 3 + baz] = max(new_dp[(eyes | 1) * 9 + bar * 3 + baz], dp[state] + foo);
              }
            }
          }
        }
      }
      bool win = new_dp[18] >= 7;
      for (int state = 9; state < 18; ++state) {
        if (new_dp[state] >= 4) {
          win = true;
        }
      }
      if (!win) {
        for (int state = 0; state < 9; ++state) {
          new_dp[state] = min(new_dp[state], 4);
        }
        extend(new_dp);
        goes[i][num] = id[encode(new_dp)];
      }
    }
  }
  int n;
  cin >> n;
  vector<int> cnt(n);
  for (int i = 0; i < 13; ++i) {
    int foo, bar;
    cin >> foo >> bar;
    --foo;
    ++cnt[foo];
  }
  vector<int> fact(n * 4 + 1), inv_fact(n * 4 + 1);
  fact[0] = fact[1] = inv_fact[0] = inv_fact[1] = 1;
  for (int i = 2; i <= n * 4; ++i) {
    fact[i] = mul(fact[i - 1], i);
    inv_fact[i] = mul(md - md / i, inv_fact[md % i]);
  }
  for (int i = 2; i <= n * 4; ++i) {
    inv_fact[i] = mul(inv_fact[i - 1], inv_fact[i]);
  }
  auto binom = [&](int x, int y) {
    return mul(fact[x], mul(inv_fact[y], inv_fact[x - y]));
  };
  auto inv_binom = [&](int x, int y) {
    return mul(inv_fact[x], mul(fact[y], fact[x - y]));
  };
  int m = all.size();
  vector<vector<int>> dp(1, vector<int>(m));
  dp[0][0] = 1;
  for (int i = 0; i < n; ++i) {
    vector<vector<int>> new_dp((i + 1) * 4 + 1, vector<int>(m));
    for (int j = 0; j <= i * 4; ++j) {
      for (int k = 0; k < m; ++k) {
        if (dp[j][k]) {
          for (int num = cnt[i]; num <= 4; ++num) {
            if (goes[k][num] != -1) {
              add(new_dp[j + num][goes[k][num]], mul(dp[j][k], binom(4 - cnt[i], 4 - num)));
            }
          }
        }
      }
    }
    swap(dp, new_dp);
  }
  int ans = 0;
  for (int i = 13; i <= n * 4; ++i) {
    for (int j = 0; j < m; ++j) {
      if (dp[i][j]) {
        add(ans, mul(dp[i][j], inv_binom(4 * n - 13, i - 13)));
      }
    }
  }
  cout << ans << "\n";
  return 0;
}

segment

f ( x ) f(x) f(x) 表示 x x x 有标记的概率, g ( x ) g(x) g(x) 表示 x x x 到根的链有标记的概率,那么:

  • 从根到目标区间的链: f ′ ( x ) = 1 2 f ( x ) , g ′ ( x ) = 1 2 g ( x ) f&#x27;(x) = \frac{1}{2} f(x), g&#x27;(x) = \frac{1}{2} g(x) f(x)=21f(x),g(x)=21g(x)
  • 目标区间: f ′ ( x ) = 1 2 ( f ( x ) + 1 ) , g ′ ( x ) = 1 2 ( g ( x ) + 1 ) f&#x27;(x)=\frac{1}{2}(f(x)+1), g&#x27;(x) = \frac{1}{2}(g(x)+1) f(x)=21(f(x)+1),g(x)=21(g(x)+1)
  • 目标区间的子树: f ′ ( x ) = f ( x ) , g ′ ( x ) = 1 2 ( g ( x ) + 1 ) f&#x27;(x)=f(x), g&#x27;(x)=\frac{1}{2}(g(x)+1) f(x)=f(x),g(x)=21(g(x)+1)
  • 被下放标记的点: f ′ ( x ) = 1 2 ( f ( x ) + g ( x ) ) , g ′ ( x ) = g ( x ) f&#x27;(x) = \frac{1}{2}(f(x)+g(x)), g&#x27;(x)=g(x) f(x)=21(f(x)+g(x)),g(x)=g(x)
  • 其他点: f ′ ( x ) = f ( x ) , g ′ ( x ) = g ( x ) f&#x27;(x)=f(x), g&#x27;(x)=g(x) f(x)=f(x),g(x)=g(x)

第一种、第二种、第四种只有 O ( n log ⁡ n ) O(n\log n) O(nlogn) 个,暴力维护;第三种可以认为是子树打标记,懒标记维护即可。

#include <bits/stdc++.h>

using namespace std;

const int md = 998244353;
const int p = (md + 1) / 2;

inline void add(int &x, int y) {
  x += y;
  if (x >= md) {
    x -= md;
  }
}

inline void sub(int &x, int y) {
  x -= y;
  if (x < 0) {
    x += md;
  }
}

inline int mul(int x, int y) {
  return (int) ((long long) x * y % md);
}

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  vector<int> f(n * 2 - 1, 0);
  vector<int> g(n * 2 - 1, 0);
  vector<int> a(n * 2 - 1, 1);
  vector<int> b(n * 2 - 1, 0);
  int ans = 0;
  int cnt = 1;
  auto apply = [&](int x, int aa, int bb) {
    g[x] = mul(g[x], aa);
    add(g[x], bb);
    a[x] = mul(a[x], aa);
    b[x] = mul(b[x], aa);
    add(b[x], bb);
  };
  function<void(int, int, int, int, int)> modify = [&](int x, int l, int r, int ll, int rr) {
    if (ll <= l && r <= rr) {
      sub(ans, f[x]);
      add(f[x], 1);
      f[x] = mul(f[x], p);
      add(ans, f[x]);
      apply(x, p, p);
    } else {
      int y = (l + r) >> 1, z = x + ((y - l + 1) << 1);
      if (a[x] != 1 && b[x] != 0) {
        apply(x + 1, a[x], b[x]);
        apply(z, a[x], b[x]);
        a[x] = 1;
        b[x] = 0;
      }
      if (ll <= y) {
        modify(x + 1, l, y, ll, rr);
      } else {
        sub(ans, f[x + 1]);
        add(f[x + 1], g[x + 1]);
        f[x + 1] = mul(f[x + 1], p);
        add(ans, f[x + 1]);
      }
      if (rr > y) {
        modify(z, y + 1, r, ll, rr);
      } else {
        sub(ans, f[z]);
        add(f[z], g[z]);
        f[z] = mul(f[z], p);
        add(ans, f[z]);
      }
      sub(ans, f[x]);
      f[x] = mul(f[x], p);
      add(ans, f[x]);
      g[x] = mul(g[x], p);
    }
  };
  while (m--) {
    int type;
    cin >> type;
    if (type == 2) {
      cout << mul(ans, cnt) << "\n";
    } else {
      int l, r;
      cin >> l >> r;
      --l;
      --r;
      modify(0, 0, n - 1, l, r);
      cnt = mul(cnt, 2);
    }
  }
  return 0;
}

minimax

考虑暴力怎么做,枚举一个 k k k ,算修改不超过 k k k 时能改变根权值的方案数。

考虑根权值的叶子对应的链,如果这条链上有值改变了,则根的权值就会改变。补集转化后,求它们都不会变的方案数,这可以用一个简单的树DP实现。注意到 k k k 1 1 1 n n n ,每个叶子的DP值至多改变一次,直接上个动态DP就行了。

#include <bits/stdc++.h>

using namespace std;

const int md = 998244353;

inline void add(int &x, int y) {
  x += y;
  if (x >= md) {
    x -= md;
  }
}

inline void sub(int &x, int y) {
  x -= y;
  if (x < 0) {
    x += md;
  }
}

inline int mul(int x, int y) {
  return (int) ((long long) x * y % md);
}

inline int inv(int a) {
  int b = md, u = 0, v = 1;
  while (a) {
    int t = b / a;
    b -= t * a;
    swap(a, b);
    u -= t * v;
    swap(u, v);
  }
  if (u < 0) {
    u += md;
  }
  return u;
}

struct node {
  int foo, bar;

  node(int foo = 1, int bar = 0): foo(foo), bar(bar) {
  }
};

node unite(const node &l, const node &r) {
  node res;
  res.foo = mul(l.foo, r.foo);
  res.bar = mul(r.bar, l.foo);
  add(res.bar, l.bar);
  return res;
}

class segtree {
 public:
  vector<node> tree;
  int n;

  void init(int sz) {
    n = sz;
    tree.resize(n * 2 - 1);
  }

  void modify(int x, int l, int r, int p, node v) {
    if (l == r) {
      tree[x] = v;
    } else {
      int y = (l + r) >> 1, z = x + ((y - l + 1) << 1);
      if (p <= y) {
        modify(x + 1, l, y, p, v);
      } else {
        modify(z, y + 1, r, p, v);
      }
      tree[x] = unite(tree[x + 1], tree[z]);
    }
  }

  void modify(int p, node v) {
    modify(0, 0, n - 1, p, v);
  }

  int query() {
    return tree[0].bar;
  }
};

struct num {
  int foo, bar;

  num(int foo = 1, int bar = 0): foo(foo), bar(bar) {
  }

  void multiply(int x) {
    if (!x) {
      ++bar;
    } else {
      foo = mul(foo, x);
    }
  }

  void divide(int x) {
    if (!x) {
      --bar;
    } else {
      foo = mul(foo, inv(x));
    }
  }

  int get() {
    return bar ? 0 : foo;
  }
};

int main() {
#ifdef wxh010910
  freopen("input.txt", "r", stdin);
#endif
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, low, high;
  cin >> n >> low >> high;
  vector<vector<int>> adj(n);
  for (int i = 0; i < n - 1; ++i) {
    int from, to;
    cin >> from >> to;
    --from;
    --to;
    adj[from].push_back(to);
    adj[to].push_back(from);
  }
  vector<int> depth(n);
  vector<int> value(n);
  vector<int> pr(n, -1);
  vector<int> sz(n, 1);
  function<void(int)> init_value = [&](int x) {
    value[x] = depth[x] & 1 ? n : -1;
    for (auto y : adj[x]) {
      if (y != pr[x]) {
        depth[y] = depth[x] + 1;
        pr[y] = x;
        init_value(y);
        if (depth[x] & 1) {
          value[x] = min(value[x], value[y]);
        } else {
          value[x] = max(value[x], value[y]);
        }
        sz[x] += sz[y];
      }
    }
    if (value[x] >= n || value[x] < 0) {
      value[x] = x;
    }
  };
  init_value(0);
  vector<int> chain;
  for (int i = value[0]; ~i; i = pr[i]) {
    chain.push_back(i);
  }
  for (int i = (int) chain.size() - 1; i; --i) {
    int from = chain[i], to = chain[i - 1];
    adj[from].erase(find(adj[from].begin(), adj[from].end(), to));
    adj[to].erase(find(adj[to].begin(), adj[to].end(), from));
    sz[from] -= sz[to];
    pr[to] = -1;
  }
  vector<vector<int>> modifies(n + 1);
  vector<int> son(n, -1);
  vector<int> top(n, -1);
  vector<int> ways(n);
  vector<bool> type(n);
  vector<segtree> seg(n);
  vector<num> light(n);
  vector<int> dp(n);
  function<void(int, int, bool, bool)> hld = [&](int x, int c, bool t, bool parity) {
    type[x] = t;
    top[x] = c;
    for (auto y : adj[x]) {
      if (y != pr[x] && (son[x] == -1 || sz[y] > sz[son[x]])) {
        son[x] = y;
      }
    }
    if (son[x] == -1) {
      ways[x] = 2;
      seg[c].init(depth[x] - depth[c] + 1);
      if (!parity) {
        if (value[x] < value[0]) {
          dp[x] = 2;
          modifies[value[0] - value[x] + 1].push_back(x);
        }
      } else {
        if (value[x] > value[0]) {
          dp[x] = 2;
          modifies[value[x] - value[0] + 1].push_back(x);
        }
      }
      seg[c].modify(depth[x] - depth[c], node(0, dp[x]));
    } else {
      hld(son[x], c, !t, parity);
      ways[x] = ways[son[x]];
      for (auto y : adj[x]) {
        if (y != pr[x] && y != son[x]) {
          hld(y, y, !t, parity);
          ways[x] = mul(ways[x], ways[y]);
          if (t) {
            light[x].multiply(dp[y]);
          } else {
            light[x].multiply((ways[y] + md - dp[y]) % md);
          }
        }
      }
      if (t) {
        dp[x] = mul(dp[son[x]], light[x].get());
        seg[c].modify(depth[x] - depth[c], node(light[x].get(), 0));
      } else {
        dp[x] = mul(ways[son[x]] + md - dp[son[x]], light[x].get());
        dp[x] = (ways[x] + md - dp[x]) % md;
        seg[c].modify(depth[x] - depth[c], node(light[x].get(), (ways[x] - mul(light[x].get(), ways[son[x]]) + md) % md));
      }
    }
  };
  vector<int> ans(n + 1);
  ans[n] = 2;
  num cur;
  for (auto x : chain) {
    for (auto y : adj[x]) {
      hld(y, y, false, depth[x] & 1);
      ans[n] = mul(ans[n], ways[y]);
      cur.multiply(dp[y]);
    }
  }
  for (int i = 1; i < n; ++i) {
    for (auto x : modifies[i]) {
      seg[top[x]].modify(depth[x] - depth[top[x]], node(0, 1));
      x = top[x];
      int new_dp = seg[x].query();
      while (value[pr[x]] != value[0]) {
        if (type[pr[x]]) {
          light[pr[x]].divide(dp[x]);
          light[pr[x]].multiply(new_dp);
        } else {
          light[pr[x]].divide((ways[x] + md - dp[x]) % md);
          light[pr[x]].multiply((ways[x] + md - new_dp) % md);
        }
        dp[x] = new_dp;
        x = pr[x];
        if (type[x]) {
          seg[top[x]].modify(depth[x] - depth[top[x]], node(light[x].get(), 0));
        } else {
          seg[top[x]].modify(depth[x] - depth[top[x]], node(light[x].get(), (ways[x] - mul(light[x].get(), ways[son[x]]) + md) % md));
        }
        x = top[x];
        new_dp = seg[x].query();
      }
      cur.divide(dp[x]);
      cur.multiply(new_dp);
      dp[x] = new_dp;
    }
    ans[i] = ans[n];
    sub(ans[i] = ans[n], cur.get());
  }
  sub(ans[n], 1);
  for (int i = n; i; --i) {
    sub(ans[i], ans[i - 1]);
  }
  for (int i = low; i <= high; ++i) {
    if (i > low) {
      cout << " ";
    }
    cout << ans[i];
  }
  cout << "\n";
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道题的简要题解: A - Dodecagon 题目描述:已知一个正十二边形的边长,求它的面积。 解题思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 题目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解题思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 题目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解题思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 题目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解题思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值