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;
    }
  • 相关阅读:
    S5PV210开发板刷机(SD卡uboot、串口+USB-OTG刷机方法)
    S5PV210启动过程分析
    总结:ARM逻辑和高级C(朱老师物联网学习)
    C语言笔记(数组地址一些细节)
    shell脚本和常用命令
    ansible
    firewalld
    LAMP架构上线动态网站WordPress
    LNMP架构上线动态网站
    Tomcat集群 Nginx负载均衡 shell脚本实时监控Nginx
  • 原文地址:https://www.cnblogs.com/yyf0309/p/12665033.html
Copyright © 2011-2022 走看看