[??记录]AT3728 [ARC087D] Squirrel Migration

command_block

2021-05-07 14:57:03

Personal

**题意** : 给出一棵 $n$ 个节点的树。 求使得 $\sum dis(i,p_i)$ 最大的排列 $p$ 的个数。 答案对 $10^9+7$ 取模。 $n\leq 5000$ ,时限$\texttt{5s}$。 ------------ 首先考虑如何求出 $\sum dis(i,p_i)$ 的最大值。 对每条边分别考虑贡献,设两侧的点数分别为 $s_1,s_2$ ,则这条边的贡献有上界 $2\min\{s_1,s_2\}$。 也可以构造出方案使得每条边的贡献达到上界。 以重心为根,对于每条边而言,深度较大的一侧则为点数较小的一侧。 记 $u$ 的子树大小为 $siz_u$ ,每条边 $u\rightarrow v$ ( $v$ 为较深一侧 ) 都要上下经过各 $siz_u$ 次。 对于每个点,寻找一个与自己处在根的不同分支的,未匹配的点,进行匹配即可。 (若某个点和同一个分支内的点匹配,则必然不优) 由于根的各个分支大小不超过整棵树的一半,故总能有这样的匹配。 接下来考虑计数。 - 只有一个重心 记第 $i$ 个分支的大小为 $c_i$。(这种情况下允许重心匹配自己) 问题可以转化为 : 有 $n$ 个点,点有颜色(同一个分支即为同色),要为每个点连出一个出边(有向边),满足每条边两端不能同色。 考虑容斥, $f(k)$ 表示钦定 $k$ 条边两端同色,其余随意的方案数,则有 : $${\rm Ans}=\sum\limits_{i=0}(-1)^if(i)$$ 接下来看 $f(k)$ 如何计算。可以先挑出 $k$ 条同色边,剩余的边随意,方案数是 $(n-k)!$。 对于第 $i$ 种颜色,钦定 $k$ 条同色边的方案数为 $\dbinom{c_i}{k}c_i^{\underline k}$ 将各个颜色的方案卷积(背包)起来即可。复杂度为 $O(n^2)$。 若利用分治 $\rm FFT$ 则可以做到 $O(n\log^2n)$。 - 有两个重心 找出连接两个重心的边,两侧的点数都恰好是 $n/2$。 只需要两侧的点之间随意匹配即可,方案数为 $(n/2)!^2$。 ```cpp #include<algorithm> #include<cstdio> #include<vector> #define pb push_back #define ll long long #define MaxN 5050 using namespace std; vector<int> g[MaxN]; int n,rt,siz[MaxN]; void dfs(int u) { siz[u]=1; for (int i=0,v;i<g[u].size();i++) if (!siz[v=g[u][i]]){ dfs(v); siz[u]+=siz[v]; } if (!rt&&siz[u]*2>=n)rt=u; } const int mod=1000000007; ll powM(ll a,int t=mod-2){ ll ret=1; while(t){ if (t&1)ret=ret*a%mod; t>>=1;a=a*a%mod; }return ret; } ll fac[MaxN],ifac[MaxN]; void Init(int n) { fac[0]=1; for (int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod; ifac[n]=powM(fac[n]); for (int i=n;i;i--) ifac[i-1]=ifac[i]*i%mod; } ll F[MaxN],G[MaxN],S[MaxN]; int main() { scanf("%d",&n);Init(n); for (int i=1,u,v;i<n;i++){ scanf("%d%d",&u,&v); g[u].pb(v);g[v].pb(u); } dfs(1);int sav=rt; for (int i=1;i<=n;i++)siz[i]=0; dfs(rt); bool fl=0; for (int i=2;i<=n;i++) fl|=(siz[i]*2==n); if (fl){ printf("%lld",fac[n/2]*fac[n/2]%mod); return 0; } int m=0;F[0]=1; for (int j=0;j<g[sav].size();j++){ int c=siz[g[sav][j]]; for (int i=0;i<=m;i++){S[i]=F[i];F[i]=0;} for (int k=0;k<=c;k++){ ll G=fac[c]*ifac[k]%mod*ifac[c-k]%mod*fac[c]%mod*ifac[c-k]%mod; for (int i=0;i<=m;i++) F[i+k]=(F[i+k]+S[i]*G)%mod; }m+=c; } ll ans=0; for (int i=0;i<=n;i++){ ll buf=F[i]*fac[n-i]%mod; if (i&1)ans-=buf;else ans+=buf; }printf("%lld",(ans%mod+mod)%mod); return 0; } ```