[2020 NOI Online]游戏

Desciption

树上有 \(2n\) 个点,每个点属于两个集合中的一个,且集合大小为 \(n\)。将两个集合里的点配对,使得恰好有 \(k\) 对从属关系,即一个点在另一个点的子树里。

Solution

已经没有什么好害怕的了这道题很像,都是点的配对,然后问有多少形成某种偏序关系的方案。前者的偏序关系就是实数的比较,所以容易想到按值排序,然后线性 dp。

类似的,这道题的偏序关系构成一棵树,所以想到用树形 dp 统计方案。定义 \(dp_{u,j}\) 表示在以 \(u\) 为根的子树中,有 \(j\) 对从属关系的方案数。

那么容易想到有两种转移方式。第一种是所有从属关系直接从子节点获得,背包即可。第二种是节点 \(u\) 和子树内的某个点配对,考虑到状态 \(dp_{u,j}\) 保证了至少有 \(j\) 对点被选,也即一个集合被选了 \(j\) 个元素,那么剩下的可选的元素个数就可以通过集合大小减去 j 来获得。而集合大小容易统计。

钦定 \(j\) 对从属关系的可重方案就为 \(F_j=dp_{1,j}\times(n-j)!\),反演一下即为答案。

#include<stdio.h>
#define N 7007
#define ll long long
#define Mod 998244353

inline int read(){
    int x=0,flag=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') flag=0;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    return flag? x:-x;
}

int n;
char S[N];

struct E{
    int next,to;
}e[N<<1];
int head[N],cnt=0,sz[N],s[2][N],fa[N],m;
ll fac[N],inv[N];

ll qpow(ll x,ll y){
    ll ret=1,tot=0;
    while(y>=(1ll<<tot)){
        if(y&(1ll<<tot)) ret=ret*x%Mod;
        x=x*x%Mod,tot++;
    }
    return ret;
}

inline void add(int id,int to){
    e[++cnt]=(E){head[id],to};
    head[id]=cnt;
}

ll dp[N][N];
void dfs(int u){
    dp[u][0]=1;
    sz[u]=1,s[u][S[u]-'0']=1;
    static ll tmp[N];
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa[u]) continue;
        fa[v]=u,dfs(v);
        for(int j=0;j<=m;j++) tmp[j]=0;
        for(int j=sz[u];~j;j--)
            for(int k=sz[v];~k;k--)
                tmp[j+k]=(tmp[j+k]+dp[u][j]*dp[v][k]%Mod)%Mod;
        for(int j=0;j<=m;j++) dp[u][j]=tmp[j];
        sz[u]+=sz[v];
        s[u][0]+=s[v][0],s[u][1]+=s[v][1];
    }
    int to=(S[u]-'0')^1;
    for(int i=s[u][to];i;i--)
        dp[u][i]=(dp[u][i]+dp[u][i-1]*(s[u][to]-i+1)%Mod)%Mod;
}

ll C(int x,int y){return x<y? 0:fac[x]*inv[y]%Mod*inv[x-y]%Mod;}

int main(){
    n=read(),m=n>>1;
    scanf("%s",S+1);
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        add(u,v),add(v,u);
    }
    dfs(1); fac[0]=1;
    for(int i=1;i<=m;i++) fac[i]=fac[i-1]*i%Mod;
    inv[m]=qpow(fac[m],Mod-2);
    for(int i=m-1;~i;i--) inv[i]=inv[i+1]*(i+1)%Mod;
    for(int i=0;i<=m;i++) dp[1][i]=dp[1][i]*fac[m-i]%Mod;
    for(int i=0;i<=m;i++){
        ll ans=0;
        for(int k=i,op=1;k<=m;k++,op=-op)
            ans=(ans+op*C(k,i)*dp[1][k]%Mod+Mod)%Mod;
        printf("%lld\n",ans);
    }
}
posted @ 2021-04-03 10:56  Kreap  阅读(42)  评论(0编辑  收藏  举报