[ARC101C] Ribbons on Tree 解题报告

给出一棵 \(n\) 个点的树,要求将树上的节点两两配对,对于一组配对 \((u,v)\) ,将 \(u\)\(v\) 的路径染色,求所有边都被染上色的方案数,答案对 \(10^9+7\) 取模。

\(n\le 5005\) ,保证 \(n\) 为偶数。

这题和氪金手游那题很类似,假设 \(f(S)\) 是在集合 \(S\) 中的边都没有被染色的方案数,其中 \(S\in E\) ,那么答案为 \(\sum (-1)^{|S|} f(S)\)

将一定不染色的边删去,一棵树就会被分为许多联通块,大小为 \(n\) 的联通块的贡献是所有配对方案的个数,也就是 \(\frac{\frac{n(n-1)}{2}\times\frac{(n-2)(n-3)}{2}\times...\times \frac{2\times 1}{2}}{(\frac{n}{2})!}=(n-1)\times(n-3)\times...\times 1\) ,其中 \(n\) 为偶数,否则不能匹配,那么有一个简单的三维 \(dp_{i,j,k}\) ,代表在 \(i\) 的子树中, \(i\) 所在联通块的大小为 \(j\) ,删去了 \(k\) 条边的方案数,转移就显而易见了。

类似氪金手游那题,可以将删去的边数这一维优化掉,因为多删一条边就会整体左移一位,放在容斥中就是整体乘上 \(-1\) ,可以在 \(j=0\) 也就是删去 \(i\) 到父亲的那条边时统计的答案乘上 \(-1\)

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int M=5005,JYY=1e9+7;

int read(){
    int x=0,y=1;char ch=getchar();
    while(ch<'0'||ch>'9') y=(ch=='-'?-1:1),ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*y;
}

int tot=0,first[M];
struct Edge{ int nxt,to; }e[M<<1];
void add(int x,int y){
    e[++tot]=(Edge){first[x],y};
    first[x]=tot;
}

int dp[M][M],sz[M],tmp[M],gs[M];
void dfs(int u,int fa){
    dp[u][1]=sz[u]=1;
    for(int i=first[u];i;i=e[i].nxt){
        int v=e[i].to;if(v==fa) continue ;
        dfs(v,u);
        for(int j=1;j<=sz[u];j++) for(int k=0;k<=sz[v];k++)
            tmp[j+k]=(tmp[j+k]+dp[u][j]*dp[v][k]%JYY)%JYY;
        sz[u]+=sz[v];
        for(int j=1;j<=sz[u];j++) dp[u][j]=tmp[j],tmp[j]=0;
    }
    for(int i=2;i<=sz[u];i+=2) dp[u][0]=(dp[u][0]-gs[i]*dp[u][i]%JYY+JYY)%JYY;
}

int n,Ans=0;
void solve(){
    n=read();gs[0]=1;
    for(int i=2;i<=n;i+=2) gs[i]=gs[i-2]*(i-1)%JYY;
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        add(x,y),add(y,x);
    }
    dfs(1,0);
    printf("%lld\n",JYY-dp[1][0]);
}

signed main(){
    // freopen("shuju.in","r",stdin);
    solve();
}
posted @ 2021-01-21 20:33  Dabuliuzp  阅读(93)  评论(0编辑  收藏  举报
/* */ 返回顶端