• 【USACO 2020 US Open Platinum】题解


    T1

    Sol

    简单题,发现实质上是用一条折线把举行分开,弯折处必须放,其余随便放不放,同时满足有些格子不能放。直接 dp 就好了。

    不过当时脑子有抽风,所以写得复杂了,分成了四种情况讨论,然后统计的是所有方案的必须选的逆元和,最后再乘上一个 (2^{tot}) ……

    Code

    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    using namespace std;
    #define LL long long
    #define go(G, x, i, v) 
      for (int i = G.hd[x], v = G.to[i]; i; v = G.to[i = G.nx[i]])
    #define inline __inline__ __attribute__((always_inline))
    inline LL read() {
      LL x = 0, w = 1;
      char ch = getchar();
      while (!isdigit(ch)) {
        if (ch == '-') w = -1;
        ch = getchar();
      }
      while (isdigit(ch)) {
        x = (x << 3) + (x << 1) + ch - '0';
        ch = getchar();
      }
      return x * w;
    }
    
    const int Max_n = 2005, mod = 1e9 + 7, inv = (mod + 1) / 2;
    int n, ans, tot, t;
    int f[Max_n], q[Max_n], g[Max_n];
    char s[Max_n][Max_n];
    
    namespace Input {
    void main() {
      n = read();
      for (int i = n; i >= 1; i--) {
        scanf("%s", s[i] + 1);
        for (int j = 1; j <= n; j++) tot += s[i][j] == '.';
        reverse(s[i] + 1, s[i] + n + 1);
      }
    }
    }  // namespace Input
    
    namespace Solve {
    void DP() {
      for (int i = 2; i <= n; i++) {
        for (int j = 0; j <= n; j++) {
          (q[j] = s[j + 1][i - 1] != 'W' ? f[j] : 0) %= mod;
        }
        for (int j = 1; j <= n; j++) (q[j] += q[j - 1]) %= mod;
        for (int j = 0; j <= n; j++) {
          g[j] = j > 0 && s[j][i] == '.' ? (LL)q[j - 1] * inv % mod * inv % mod : 0;
          (g[j] += f[j] % mod) %= mod;
        }
        swap(g, f);
      }
      for (int i = 0; i < n; i++) f[i] = s[i + 1][n] != 'W' ? f[i] : 0;
      for (int i = 1; i < n; i++) (f[i] += f[i - 1]) %= mod;
    }
    void main() {
      t = 1;
      while (tot--) t = 2 * t % mod;
      for (int i = 1; i <= n; i++) f[i] = s[i][1] == '.' ? (LL)inv * inv % mod : 0;
      DP();
      (ans += (LL)f[n] * t % mod * 2 % mod) %= mod;
      (ans += (LL)f[n - 1] * t % mod) %= mod;
      f[0] = 1;
      for (int i = 1; i <= n; i++) f[i] = 0;
      DP();
      (ans += (LL)f[n] * t % mod) %= mod;
      (ans += (LL)f[n - 1] * t % mod * inv % mod) %= mod;
      cout << ans << endl;
    }
    }  // namespace Solve
    
    int main() {
    #ifndef ONLINE_JUDGE
      freopen("A.in", "r", stdin);
      freopen("A.out", "w", stdout);
    #endif
      Input::main();
      Solve::main();
    }
    

    T2

    Sol

    题意是对于 (n!) 种置换,每种置换的所有环长的 (lcm) 的乘积。

    不难发现可以转化为求每个质因数的指数,最后乘起来就行了。

    那么重点在于怎么求指数。

    对于 (p) ,其实并不需要一一处理恰好包含 (p^i) 的置换个数,事实上设 (F(x))(x|lcm) 的置换个数,那么 (p) 的指数就是 (sum F(p^i))

    接下来进一步转化为怎么求 (F(x)) ,若 (x|lcm) ,那条件应该是所有环长中,至少有一个环长 (len) 满足 (x|len) 即可。

    正难则反,转为求 (f(t)) 为构造的环长总合目前为 (t),没有任何一个环长满足 (x|len) 的置换数,则 (F(x)=n!-f(n))

    接着我们可以设函数 (g(t)) 为构造的环长总合目前为 (t),且所有环长都满足 (x|len)

    则有如下转移:

    [f(t) = t!-sum_{j > 0} g(j)*f(t-j)*C^{j}_{t} ]

    由定义可知,正确性显然。

    然后问题又变成了怎么求 (g) ,我们有如下转移式:

    [g(t)=sum_{j > 0, x|j} C_{t-1}^{j-1}*(j-1)! * g(t - j) ]

    其中 (j) 为 1 所在的联通块大小,正确性显然。

    故对于每一个 (p) ,我们都分别求出 (F(p^i)) ,然后计算答案。

    根据转移式可发现,若要求得 (f(n))(f, g) 所需使用的下标仅有 (lfloor frac{n}{x} floor) 个,故最终所有 (dp) 的总复杂度也就是 (O(n^2))

    Code

    #include <cstdio>
    #include <iostream>
    using namespace std;
    #define LL long long
    typedef unsigned long long uLL;
    #define go(G, x, i, v) 
      for (int i = G.hd[x], v = G.to[i]; i; v = G.to[i = G.nx[i]])
    #define inline __inline__ __attribute__((always_inline))
    inline LL read() {
      LL x = 0, w = 1;
      char ch = getchar();
      while (!isdigit(ch)) {
        if (ch == '-') w = -1;
        ch = getchar();
      }
      while (isdigit(ch)) {
        x = (x << 3) + (x << 1) + ch - '0';
        ch = getchar();
      }
      return x * w;
    }
    
    const int Max_n = 7.5e3 + 5;
    int n, mod, ans;
    int C[Max_n][Max_n], fac[Max_n];
    
    namespace FastMod {
    uLL b, m;
    void init(int x) { b = x, m = uLL((__int128(1) << 64) / b); }
    uLL Mod(uLL a) {
      uLL q = (uLL)((__int128(m) * a) >> 64);
      uLL r = a - q * b;  // can be proven that 0 <= r < 2*b
      return r >= b ? r - b : r;
    }
    }  // namespace FastMod
    using FastMod::Mod;
    
    void M(int &x) { x = x >= mod - 1 ? x - mod + 1 : x; }
    
    namespace Input {
    void main() { n = read(), mod = read(); }
    }  // namespace Input
    
    namespace Init {
    void main() {
      FastMod::init(mod - 1);
      for (int i = 0; i <= n; i++) {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++) M(C[i][j] = C[i - 1][j - 1] + C[i - 1][j]);
      }
      fac[0] = 1;
      for (int i = 1; i <= n; i++) fac[i] = Mod((uLL)fac[i - 1] * i);
    }
    }  // namespace Init
    
    namespace Solve {
    int f[Max_n], g[Max_n];
    bool vis[Max_n];
    int ksm(int a, int b) {
      int res = 1;
      for (; b; b >>= 1, a = (LL)a * a % mod)
        if (b & 1) res = (LL)res * a % mod;
      return res;
    }
    int DP(int x) {
      g[0] = f[0] = 1;
      for (int i = x; i <= n; i += x) {
        g[i] = 0;
        for (int j = x; j <= i; j += x)
          M(g[i] += Mod(Mod((uLL)C[i - 1][j - 1] * fac[j - 1]) * g[i - j]));
      }
      for (int i = n % x; i <= n; i += x) {
        f[i] = fac[i];
        for (int j = x; j <= i; j += x) 
          M(f[i] += mod - 1 - Mod(Mod((uLL)C[i][j] * g[j]) * f[i - j]));
      }
      return Mod(fac[n] - f[n] + mod - 1);
    }
    void main() {
      ans = 1;
      for (int i = 2; i <= n; i++)
        if (!vis[i]) {
          for (int j = i; j <= n; j += i) vis[j] = 1;
          for (int j = i; j <= n; j *= i) ans = (LL)ans * ksm(i, DP(j)) % mod;
        }
      cout << ans << endl;
    }
    }  // namespace Solve
    
    int main() {
    #ifndef ONLINE_JUDGE
      freopen("B.in", "r", stdin);
      freopen("B.out", "w", stdout);
    #endif
      Input::main();
      Init::main();
      Solve::main();
    }
    

    T3

    Sol

    神题。

    先想一下怎么算答案,我们发现,不管这 (K) 个点最开始在哪里,我们都必定可以将它们移到任意 (K) 个被指定的点上去,所以我们的方案数肯定最大为 (K!)

    我们将所有极长的,中间点度数均为 2 的路径缩成一条边,将树变成一棵每个点度数均不为 2 的新树,对于一条边 (i) ,我们有 (A_i)(u) 这边的子树大小,(B_i)(v) 这边的子树大小,(C_i) 为这条路径上的点数,则有 (A_i-1+B_i-1+C_i=n)

    考虑对于一个 (K) 和一条边 (i) ,若有 (Kge A_i-1+B_i-1) ,则 (u, v) 两个子树内的点必然不能交换,且中间的链上始终有 (K-(A_i-1)-(B_i-1)) 个元素,这些元素的顺序无法被改变。

    反之若 (K< A_i-1+B_i-1) ,发现必然可以在不影响其他元素的情况下,交换 ((u, v)) 链上的任意两个相邻元素,也就等价于可以交换链上任意两个元素。

    对于一个 (K) ,将所有满足 (K < A_i-1+B_i-1) 的边连起来,发现对于每个联通块内都必然可以相互交换,同时对于所有能够“到达”这个联通块内的点,也可以做出交换。

    对于一个联通块,如何计算能够到达它的点呢?我们发现如果联通块内连出去一条边,但是这条边并不满足 (K < A_i-1+B_i-1) ,设 (A_i) 是相对于联通块来说在外面的子树,除去此子树及此链以外的点数自然为 (n-(A_i-1)-C_i)

    由于不满足等式,则 (n-(A_i-1)-C_i < K) ,所以此子树最少也要剩下 (K-(n-(A_i-1)-C_i) = A_i-1+C_i+K-n) 个点不能到达联通块。

    故对于当前 (K) 所构成的所有联通块,对于其中一个联通块 (T),设其点数为 (x) ,和联通块相连但不满足要求要求的边的编号集合 (S={i | v_iin T and u_i otin T}) ,设此集合大小为 (y) ,则联通块内可交换集合大小为:

    [f(T)=K-sum_{i in S} A_i - 1 + C_i + K - n ]

    将常数项拆出来,并且由于其意义,实际上 (sum_{i in S} A_i - 1 + C_i=n-x+y)

    那么可将式子进一步化为:

    [f(T)=K - (n-x) - (K-n+1) * y ]

    由此,对于一个 (K) 而言,其答案就是 (frac{K!}{prod f(T)!}) ,意义在开头已经说过了,(K!) 是放在某 (K) 个指定位置的方案数,然后除去所有可交换的位置集合方案数。

    那么考虑从大到小枚举 (K) ,就能使得边不断被加入,用并查集即可轻松维护 (x)(y)

    对于每个 (K) 来说,不用管那么多,直接枚举当前存在的所有联通块即可。

    因为对于任意一条边来说,它不被连上当且仅当 (K>(A_i-1)+(B_i - 1)) ,也就是 (K > n - C_i) ,也就是说它只会在前 (C_i) 大的 (K) 里作出贡献,也就是说所有 (K) 的联通块数的和为 (sum C_i = O(n)) ,所以直接枚举的复杂度肯定是对的。

    Code

    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    using namespace std;
    #define LL long long
    #define go(G, x, i, v) 
      for (int i = G.hd[x], v = G.to[i]; i; v = G.to[i = G.nx[i]])
    #define inline __inline__ __attribute__((always_inline))
    inline LL read() {
      LL x = 0, w = 1;
      char ch = getchar();
      while (!isdigit(ch)) {
        if (ch == '-') w = -1;
        ch = getchar();
      }
      while (isdigit(ch)) {
        x = (x << 3) + (x << 1) + ch - '0';
        ch = getchar();
      }
      return x * w;
    }
    
    const int Max_n = 1e5 + 5, mod = 1e9 + 7;
    struct graph {
      int hd[Max_n];
      int cntr, nx[Max_n << 1], to[Max_n << 1];
      void addr(int u, int v) {
        cntr++;
        nx[cntr] = hd[u], to[cntr] = v;
        hd[u] = cntr;
      }
    };
    struct path {
      int u, v, len;
    };
    
    int n;
    int fac[Max_n], ifac[Max_n];
    int deg[Max_n], bf[Max_n], nx[Max_n];
    graph G;
    int cnt, f[Max_n], x[Max_n], y[Max_n];
    path K[Max_n];
    void remove(int x) {
      nx[bf[x]] = nx[x], bf[nx[x]] = bf[x];
    }
    
    namespace Input {
    void main() {
      n = read();
      for (int i = 1; i < n; i++) {
        int u = read(), v = read();
        G.addr(u, v), G.addr(v, u);
        deg[u]++, deg[v]++;
      }
    }
    }  // namespace Input
    
    namespace Init {
    int ksm(int a, int b = mod - 2) {
      int res = 1;
      for (; b; b >>= 1, a = (LL)a * a % mod)
        if (b & 1) res = (LL)res * a % mod;
      return res;
    }
    void build(int x, int fa, int Fa, int dep) {
      if (deg[x] != 2) {
        if (x != nx[0]) K[++cnt] = (path){Fa, x, dep + 1};
        Fa = x, dep = 0;
      }
      go(G, x, i, v) if (v != fa) build(v, x, Fa, dep + 1);
    }
    void main() {
      fac[0] = 1;
      for (int i = 1; i <= n; i++) fac[i] = (LL)fac[i - 1] * i % mod;
      ifac[n] = ksm(fac[n]);
      for (int i = n; i ; i--) ifac[i - 1] = (LL)ifac[i] * i % mod;
      int L = 0;
      for (int i = 1; i <= n; i++)
        if (deg[i] != 2) {
          bf[i] = L, nx[L] = i, L = i;
          f[i] = i, x[i] = 1, y[i] = deg[i];
        }
      build(nx[0], 0, 0, 0);
    }
    }  // namespace Init
    
    namespace Solve {
    bool cmp(path a, path b) {
      return a.len < b.len;
    }
    int ans[Max_n];
    int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
    void merge(int u, int v, int len) {
      u = find(u), v = find(v);
      if (u == v) return;
      f[v] = f[u], x[u] += x[v] + len - 2, y[u] += y[v] - 2;
      remove(v);
    }
    void main() {
      sort(K + 1, K + cnt + 1, cmp);
      int pos = 1;
      ans[n] = fac[n];
      for (int k = n - 1; k; k--) {
        ans[k] = fac[k];
        while (pos <= n && n - K[pos].len > k) merge(K[pos].u, K[pos].v, K[pos].len), pos++;
        for (int i = nx[0]; i; i = nx[i]) {
          ans[k] = (LL)ans[k] * ifac[k - (n - x[find(i)]) - (k - n + 1) * y[find(i)]] % mod;
        }
      }
      for (int i = 1; i <= n; i++) printf("%d
    ", ans[i]);
    }
    }  // namespace Solve
    
    int main() {
    #ifndef ONLINE_JUDGE
      freopen("C.in", "r", stdin);
      freopen("C.out", "w", stdout);
    #endif
      Input::main();
      Init::main();
      Solve::main();
    }
    
  • 相关阅读:
    【转】 Linux Core Dump 介绍
    【转】 设定linux 系统可用资源
    Python for 循环 失效
    transition 平移属性实现 横向整屏 滚动
    vue 插槽的使用
    vue pc商城仿网易严选商品的分类效果
    干货-vue 中使用 rxjs 进行非父子组件中传值
    vue 2.0 脚手架项目中使用 cross-env 分环境打包
    什么是闭包,有哪些优缺点呢?
    滚动视差
  • 原文地址:https://www.cnblogs.com/luoshuitianyi/p/12801480.html
Copyright © 2020-2023  润新知