[ARC087]F - Squirrel Migration 树+容斥原理

20 篇文章 0 订阅
8 篇文章 0 订阅

题面
先考虑每条边 (u,v) 的松鼠通过量,最大显然为 2min(sizeu,sizev) ,这里的 size 指去掉这条边后的两个子树大小。所以我们分两种情况。
有两个重心:答案显然为 (n2)!2
有一个重心:我们设去掉重心后的子树为 T1,T2,...,Tr T 为它们的并。
要让答案最大,那么不能存在有某只松鼠从Ti中跑回 Ti 中。
我们考虑容斥原理计算方案数:

Ans=ST(1)|S|fS

其中 fS 为集合 S 中的松鼠都跑回本组中的方案数。所以我们只需计算gk=|S|=kfS,也就是有多少组 (s1,s2,...,sk),(t1,t2,...,tk) ,满足 s1<s2<..<sk si ti 在同一组中。这个DP一下就好,设 gi,j 为考虑前i组,选了j个的方案,那么 gi,j+=gi1,jkCksizeiAksizei 。所以答案即为:
Ans=i=0n1(1)igi(Ni)!

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=5010;
const int mod=1000000007;
struct edge
{
    int t;
    edge *next;
}*con[maxn];
int n,sz[maxn];
ll jie[maxn],inv[maxn],g[2][maxn];
ll ksm(ll a,int b){ll r=1;for(;b;b>>=1){if(b&1) r=r*a%mod;a=a*a%mod;}return r;}
ll A(int a,int b){return jie[a]*inv[a-b]%mod;}
ll C(int a,int b){return A(a,b)*inv[b]%mod;}
void ins(int x,int y)
{
    edge *p=new edge;
    p->t=y;
    p->next=con[x];
    con[x]=p;
}
void getroot(int v,int fa,int &root)
{
    bool flag=1;
    sz[v]=1;
    for(edge *p=con[v];p;p=p->next)
        if(p->t!=fa)
        {
            getroot(p->t,v,root);
            sz[v]+=sz[p->t];
            if(sz[p->t]*2>n) flag=0;
        }
    if(sz[v]*2<n) flag=0;
    if(flag) root=(root?-1:v);  
}
ll solve()
{
    int rt=0;
    getroot(1,-1,rt);
    if(rt==-1) return jie[n/2]*jie[n/2]%mod;
    for(edge *p=con[rt];p;p=p->next)
        sz[p->t]=-(sz[p->t]>sz[rt]?n-sz[rt]:sz[p->t]);
    for(int i=1;i<=n;i++)
        sz[i]=(sz[i]>0?0:-sz[i]);       
    int tot=0;
    g[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        tot+=sz[i];
        memset(g[i&1],0,sizeof(g[i&1]));
        for(int j=0;j<=tot;j++)
            for(int k=0;k<=min(sz[i],j);k++)
                g[i&1][j]=(g[i&1][j]+g[(i&1)^1][j-k]*C(sz[i],k)%mod*A(sz[i],k)%mod)%mod;
    }
    ll ans=0;
    for(int i=0,r=1;i<=n;i++,r=-r)
        ans=(ans+(g[n&1][i]*jie[n-i]%mod*r+mod)%mod)%mod;
    return ans; 
}
int main()
{
    scanf("%d",&n);
    jie[0]=1;
    for(int i=1;i<=n;i++)
        jie[i]=jie[i-1]*i%mod;
    inv[n]=ksm(jie[n],mod-2);
    for(int i=n-1;i>=0;i--)
        inv[i]=inv[i+1]*(i+1)%mod;  
    //for(int i=0;i<=n;i++)
    //  cout<<jie[i]<<' '<<inv[i]<<endl;    
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ins(x,y);
        ins(y,x);
    }
    printf("%lld",solve());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值