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(n\log 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\n", ans[i].v);
  }
  return 0;
}
posted @ 2020-04-09 10:17  阿波罗2003  阅读(723)  评论(0编辑  收藏  举报