[ARC101C] Ribbons on Tree 解题报告

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

(nle 5005) ,保证 (n) 为偶数。

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

将一定不染色的边删去,一棵树就会被分为许多联通块,大小为 (n) 的联通块的贡献是所有配对方案的个数,也就是 (frac{frac{n(n-1)}{2} imesfrac{(n-2)(n-3)}{2} imes... imes frac{2 imes 1}{2}}{(frac{n}{2})!}=(n-1) imes(n-3) imes... imes 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
",JYY-dp[1][0]);
}

signed main(){
    // freopen("shuju.in","r",stdin);
    solve();
}
原文地址:https://www.cnblogs.com/wzp-blog/p/14310316.html