hdu5103 RootedTree 状压dp,树形dp

这题说的是给了n(14)个点,每个点都以他 为根的最大可容的孩子个数和最小的可溶孩子个数L[i] ,R[i]

问这n个点形成一棵树有多少种形态

我们让 dp[i][S] 表示 一 i为根节点 的 拥有孩子S(二进制数)状态的 方案数 , sub[S] , 表示 以 S 状态表示的 森林的 方案数, sum[S] 表示 一S 状态的 有根树 的 方案数

可以知道 

dp[i][S] = sub[ S^(1<<i) ] { L[i]<=|S|<=R[i]   }

sum[S] = dp[i][S] { i=0,1,2,3,,,n-1 | S&1<<i!=0  }

sub[S] = sub[S] +  sum[H]*sub[S^H]{ H 为s 的 子集 ,然后 先固定 S 中第一个不是点 不是0 的一定要在 H 中, 这样是 为了保证 不会出现一个点被算了两次,可能这个点在枚举时存在对称性 , 我们一旦确定一个点在那个位置就可以避免这种情况的出现 }

#include<iostream>
#include<cstring>
#include<cstdio>
#include<ostream>
#include<istream>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<vector>
#define fi first
#define se second
#define pii pair<int,int>
#define inf (1<<30)
#define eps 1e-8
#define ll long long
using namespace std;
const int maxn=110005;
const ll mod=1000000007;
int n;
int d[20][2];
int num[1<<14];
ll sum[1<<14];
ll sub[1<<14];
ll dp[1<<14][15];
ll Dp(int s,int c);
ll Sum(int s);
ll Sub(int s)
{
    if(s==0)
        return 1;
    if(sub[s]!=-1)
        return sub[s];
    sub[s]=0;
    int id[15];
    int cnt=0;
    for(int i=0;i<n;i++) {
        if(s&(1<<i)) {
            id[cnt++]=i;
        }
    }
    for(int i=1;i<(1<<cnt);i+=2) {
        int s1=0;
        for(int j=0;j<cnt;j++) {
            if(i&(1<<j)) {
                s1^=(1<<id[j]);
            }
        }
        sub[s]+=Sum(s1)*Sub(s^s1)%mod;
        sub[s]%=mod;
    }
    return sub[s];
}
ll Dp(int s,int c)
{
    return Sub(s^(1<<c));
}
ll Sum(int s)
{
    if(sum[s]!=-1)
        return sum[s];
    sum[s]=0;
    for(int i=0;i<n;i++) {
        if(s&(1<<i)) {
            if(num[s]>=d[i][0]&&num[s]<=d[i][1])
                sum[s]=(sum[s]+Dp(s,i))%mod;
        }
    }
    return sum[s];
}
void pre()
{
    memset(num,0,sizeof(num));
    for(int i=0;i<(1<<14);i++) {
        int o=0;
        for(int j=0;j<14;j++) {
            if(i&(1<<j))
                o++;
        }
        num[i]=o;
    }
}
int main()
{
    pre();
    int t;
    scanf("%d",&t);
    while(t--) {
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%d%d",&d[i][0],&d[i][1]);
        memset(sum,-1,sizeof(sum));
        memset(sub,-1,sizeof(sub));
        memset(dp,-1,sizeof(dp));
        printf("%I64d\n",Sum((1<<n)-1));
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值