Loading

AT_arc101_c

AT_arc101_c
子集容斥的一道题。
正难则反是好想到的事实上覆盖一次以上就满足题意,这种一般都是反着做,也就是说一边是有限个,另一边是无限个考虑有限个的,可以枚举哪些边没被覆盖,然后做。

\[\sum_{i=0}^n \binom{n}{i}ways(i)(-1)^i \]

其中ways(i)表示选出i个边段掉的剩下点配对方案数,事实上由于边互不相同,所以这个binom不必要乘,因该写成

\[\sum_{i=0}^n ways(i)(-1)^i \]

问题在于求ways,把边断掉后形成若干连通块,那么每个连通块点配对书相乘就是答案。
当然可以2^n枚举,考虑优化。
容易想到dp,一个显然的dp思路是把所有东西记进去\(dp_{u,i,j}\)表示以u为根子树,有i个断边,u所在连通块大小为j,方案数(不包含u所在连通块配对方案)
转移就是

\[dp_{u,i,j}+=dp_{v,k,j1}dp_{u,i-k,j-j1}+dp_{v,k,j1}dp_{u,i-k-1,j}sum_{j1} \]

但是直接T飞。
问题在于记的太多,一个经典解决方法就是直接将容斥系数都融入到dp中(还有一种就是开科技NTT)
那么记\(dp_{u,i}\)表示以u为根子树,u所在连通块大小为j,方案数(不包含u所在连通块配对方案)
那么转移

  • u与v不断,\(dp_{u,k}\)<-\(dp_{v,j}dp_{u,k-j}\)
  • u与v断,\(dp_{u,k}\)<-\((-sum_j)dp_{v,j}dp_{u,k}\)

那么最后就是求和,注意把1所在连通块给算进去

\[\sum_{i=0}^ndp_{1,i}sum_{i} \]

注意

  • 树形dp上下界优化,否则就是\(n^3\)
  • 由于每次dp要在旧的值基础上,而新旧值放在一个数组中就会紊乱所以要开一个中间数组
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
void rd(ll& x)
{
    char ch=getchar();
    ll f=1;x=0;
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-48;
        ch=getchar();
    }
    x=x*f;
}
const ll N_ = 5005,mod=1e9+7;
ll ans,N,dp[N_][N_],S[N_],siz[N_],tmp[N_];
vector<ll> q[N_];
inline ll MOD(ll x)
{
    x+=mod;
    return x-x/mod*mod;
}
void dfs(ll u,ll f)
{
    siz[u]=1;
    dp[u][1]=1;
    for(auto v:q[u])
    {
        if(v==f)continue;
        dfs(v,u);
        ll A=siz[u];
        siz[u]+=siz[v];
        for(int i=0;i<=siz[u];i++)tmp[i]=0;
        for(int k=siz[u];k>=1;k--)
        {
            for(int j=max(1ll,k-A);j<=min(k-1*1ll,siz[v]);j++)tmp[k]=MOD(tmp[k]+MOD(dp[u][k-j]*1ll*dp[v][j]));
            if(k<=siz[u])for(int j=1;j<=siz[v];j++)tmp[k]=MOD(tmp[k]+MOD(MOD(dp[v][j]*1ll*dp[u][k])*1ll*(-S[j])));
        }    
        for(int i=1;i<=siz[u];i++)dp[u][i]=tmp[i];
    }
}
int main()
{
    // freopen("data.in","r",stdin);
    rd(N);
    for(int i=1;i<N;i++)
    {
        ll u,v;
        rd(u);rd(v);
        q[u].push_back(v);
        q[v].push_back(u);
    }
    S[0]=1;
    for(int i=2;i<=N;i+=2)S[i]=S[i-2]*1ll*(i-1)%mod;
    dfs(1,-1);
    for(int i=1;i<=N;i++)ans=(ans+dp[1][i]*1ll*S[i])%mod,ans%=mod;
    cout<<ans;
    return 0;
}

特别的如果用填表法,上下界比较复杂,用刷表法直接1-siz[u],1-siz[v]枚举即可,所以树形dp建议用刷表法
树上容斥,一般都要开一个中间数组
小结:1.覆盖问题,算有限个的。2.dp维度多考虑将容斥系数融入

posted @ 2026-07-22 15:37  leaf_ydc  阅读(4)  评论(0)    收藏  举报