zoukankan      html  css  js  c++  java
  • loj 3285 「USACO 2020 US Open Platinum」Circus

    题目传送门

      传送门

      显然当 $n = K$ 的时候,答案为 $K!$,下面将不再考虑。

      考虑任选 $K$ 个位置,显然,任意一个初始状态都可以通过一些移动使得所有奶牛都在这 $K$ 个位置上。因此我们只统计这 $K$ 个位置上有多少种不同的初始状态。

      考虑某个初始状态 $x$ 能够到达 $y$,那么相当于是对 $x$ 乘上了某一个置换 $f$。考虑两个属于不同的等价类的初始状态 $x, y$,如果我们把等价关系看做边,我们找一个生成树,显然我们能在两个连通块里找一个一样的生成树,因此如果在第一个等价类 $a$ 通过置换 $t$ 得到新的初始状态,那么和第二个等价类中和 $a$ 相应的状态通过置换 $t$ 也能得到新的初始状态。

      因此所有等价类的大小都是相同的。因此答案是 $frac{K!}{sz}$。

      考虑怎么计算一个等价类的大小。考虑判断能不能交换两个位置上的奶牛,然后使得剩下的维持原状。

      先考虑一些必要条件,注意到在任意度为 2 的点上不可能完成交换,考虑中间的点的度数都是 2,两端的点的度数都不是 2 的子图,下面我们称它为链。如果一侧子树内有 $A$ 个点,另一侧有 $B$ 个点,链上有 $C$ 个点,那么当 $K geqslant (A - 1) + (B - 1)$,能够到达一侧的 $(A - 1)$ 个点始终不能到达另一侧,链上恰好始终有 $K - (A - 1) - (B - 1)$ 个点。

      我们来证明当不违反上述条件时,可以完成交换。即不穿过满足 $K geqslant (A - 1) + (B - 1)$ 的链,能够使得它们互相到达。

      先考虑将某一个奶牛移动到右侧。先考虑两端的度数都大于等于 $3$ 的情形。

      假设左侧在链上有 $l$ 个点,子树内(不含根)有 $a$ 个点,右侧链上有 $r$ 个点,右侧子树内有 $b$ 个点。根据条件有 $a + l + r + b + 1 < (A - 1) + (B - 1)$。

    • 如果 $r + b < B - 1$ 直接移过去就完事了。
    • 否则有 $a + l < A - 2$,
      • 如果和奶牛相连的空位子形成一条链,那么找一个子树内不在链上的奶牛移到链上,然后把这个奶牛移动到这个位置上,然后把剩下的奶牛往子树内移动,直到 $r + b < B - 1$。
      • 否则随意移动到一棵子树内,然后将在链上的下一个点移开,直到形成一条链或者 $r + b < B - 1$。

      当一端度数为 $1$,有 $r + b + 1  < B - 1$,显然可行。

      然后考虑完成交换,这个时候只用证明任意两个链上相邻的奶牛可以交换位置就可以了。此时满足 $a + l + r + b + 2 < (A - 1) + (B - 1)$。即 $a + l + r + b < A + B - 4$,此时要么 $a + l leqslant A - 3$,要么 $b + r leqslant B - 3$。不妨设是 $a + l leqslant A - 3$,这个时候只用将 $l$ 个奶牛全部移动到子树内,如果使得空的位置形成了一条链,那么把一个不在链上的奶牛,移到链上就可以了。然后再子树中剩下至少 3 个空位可以完成交换。

      现在来说明一下可以使得剩下的奶牛位置不变,注意到操作总是可逆的,$x, y$ 交换完成后,把 $y$ 看成 $x$,把 $x$ 看成 $y$,因为存在初始状态到达它的方案,所以也存在它到初始状态的方案,操作结束后 $x, y$ 的位置是相反的。

      如果两个位置能交换,那么它们连一条边,$sz = prod s_i!$,$s_i$ 是每个连通块的大小。

      然后从大到小枚举 $K$ ,如果一条链满足 $K < (A - 1) + (B - 1)$ 就把它两端连接上。然后问题变成能够到达每个连通块的有多少点。直接做不好做,但根据和它相邻的被断掉的边计算不能到它的点有多少个,把它们减去就行了。

      另外注意到连通块数等于链数加一,一条链在存在的次数等于它的链长,所以对于每个 $K$ 暴力枚举所有连通块复杂度为 $O(n)$。剩下的并查集维护即可。

      时间复杂度 $O(nlog n)$。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef bool boolean;
    
    #define ll long long
    
    void exgcd(int a, int b, int& x, int& y) {
      if (!b) {
        x = 1, y = 0;
      } else {
        exgcd(b, a % b, y, x);
        y -= (a / b) * x;
      }
    }
    
    int inv(int a, int n) {
      int x, y;
      exgcd(a, n, x, y);
      return (x < 0) ? (x + n) : (x);
    }
    
    const int Mod = 1e9 + 7;
    
    template <const int Mod = :: Mod>
    class Z {
      public:
        int v;
    
        Z() : v(0) {	}
        Z(int x) : v(x){	}
        Z(ll x) : v(x % Mod) {	}
    
        friend Z operator + (const Z& a, const Z& b) {
          int x;
          return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
        }
        friend Z operator - (const Z& a, const Z& b) {
          int x;
          return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
        }
        friend Z operator * (const Z& a, const Z& b) {
          return Z(a.v * 1ll * b.v);
        }
        friend Z operator ~(const Z& a) {
          return inv(a.v, Mod);
        }
        friend Z operator - (const Z& a) {
          return Z(0) - a;
        }
        Z& operator += (Z b) {
          return *this = *this + b;
        }
        Z& operator -= (Z b) {
          return *this = *this - b;
        }
        Z& operator *= (Z b) {
          return *this = *this * b;
        }
        friend boolean operator == (const Z& a, const Z& b) {
          return a.v == b.v;
        } 
    };
    
    Z<> qpow(Z<> a, int p) {
      Z<> rt = Z<>(1), pa = a;
      for ( ; p; p >>= 1, pa = pa * pa) {
        if (p & 1) {
          rt = rt * pa;
        }
      }
      return rt;
    }
    
    typedef Z<> Zi;
    
    typedef class Path {
      public:
        int u, v, su, sv, sab;
    
        Path(int u, int v, int su, int sv) : u(u), v(v), su(su), sv(sv), sab(su + sv - 2) { }
    
        bool operator < (Path b) const {
          return sab > b.sab;
        }
    } Path;
    
    const int N = 1e5 + 5;
    
    int n;
    vector<int> G[N];
    Zi fac[N], _fac[N];
    
    void init_fac(int n) {
      fac[0] = 1;
      for (int i = 1; i <= n; i++) {
        fac[i] = fac[i - 1] * i;
      }
      _fac[n] = ~fac[n];
      for (int i = n; i; i--) {
        _fac[i - 1] = _fac[i] * i;
      }
    }
    
    int sz[N];
    vector<Path> P;
    int get_sz(int p, int fa) {
      sz[p] = 1;
      for (auto e : G[p]) {
        if (e ^ fa) {
          sz[p] += get_sz(e, p);
        }
      }
      return sz[p];
    }
    void dfs(int p, int fa, int u, int su) {
      if (G[p].size() ^ 2u) {
        if (u) {
          P.emplace_back(u, p, su, sz[p]);
        }
        for (auto e : G[p]) {
          if (e ^ fa) {
            dfs(e, p, p, n - sz[e]);
          }
        }
      } else {
        for (auto e : G[p]) {
          if (e ^ fa) {
            dfs(e, p, u, su);
          }
        }
      }
    }
    
    int uf[N];
    int f[N], g[N];
    int pre[N], suf[N];
    
    void remove(int x) {
      pre[suf[x]] = pre[x];
      suf[pre[x]] = suf[x];
    }
    
    int find(int x) {
      return uf[x] == x ? x : (uf[x] = find(uf[x]));
    }
    void merge(Path p) {
      int u = p.u, v = p.v;
      int fu = find(u);
      int fv = find(v);
      f[fu]--, f[fv]--;
      g[fu] -= p.su - 1, g[fv] -= p.sv - 1;
      f[fu] += f[fv];
      g[fu] += g[fv];
      uf[fv] = fu;
    }
    
    Zi ans[N];
    int main() {
      scanf("%d", &n);
      for (int i = 1, u, v; i < n; i++) {
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
      }
      init_fac(n);
      int Rt = 1;
      while (G[Rt].size() == 2u) Rt++;
      get_sz(Rt, 0);
      dfs(Rt, 0, 0, 0);
      sort(P.begin(), P.end());
      pre[0] = 0, suf[0] = 1;
      for (int i = 1; i <= n; i++) {
        pre[i] = i - 1, suf[i] = i + 1;
      }
      pre[n + 1] = n, suf[n + 1] = n + 1;
      for (int i = 1; i <= n; i++) {
        if (G[i].size() == 2u) {
          remove(i);
        } else {
          uf[i] = i;
        }
      }
      for (auto p : P) {
        f[p.u]++, f[p.v]++;
        g[p.u] += p.su - 1;
        g[p.v] += p.sv - 1;
      }
      auto it = P.begin(), _it = P.end();
      for (int k = n - 1; k; k--) {
        while (it != _it && (*it).sab > k) {
          merge(*it);
          it++;
        }
        ans[k] = fac[k];
        for (int i = suf[0]; i <= n; i = suf[i]) {
          if (find(i) != i) {
            remove(i);
            continue;
          }
          int t = k - k * f[i] + g[i];
          ans[k] *= _fac[t];
        }
      }
      ans[n] = fac[n];
      for (int i = 1; i <= n; i++) {
        printf("%d
    ", ans[i].v);
      }
      return 0;
    }
  • 相关阅读:
    用Python完成一个汇率转换器
    鸿蒙如何用JS开发智能手表App
    鸿蒙如何用JS开发智能手表App
    SAP Spartacus SplitViewComponent Migration 的一个具体例子
    SAP Spartacus B2B 页面 Popover Component 的条件显示逻辑
    SAP Spartacus 升级时关于 schematics 的更新
    SAP Spartacus B2B 页面 Disable 按钮的显示原理
    SAP Spartacus B2B 页面 Disable Confirmation 对话框的显示原理
    通过 Feature Level 动态控制 SAP Spartacus 的页面显示
    SAP Commerce Cloud Build Manifest Components
  • 原文地址:https://www.cnblogs.com/yyf0309/p/12665033.html
Copyright © 2011-2022 走看看