【BZOJ4455】小星星(动态规划,容斥)

题面

BZOJ
洛谷
Uoj

题解

题意说简单点就是给定一张 n n 个点的图和一棵n个点的树,现在要让图和树之间的点一一对应,并且如果树上存在一条边,那么图上对应的点对之间也要存在边。

我们直接求解显然很麻烦,一一对应是一个很不好算的东西。
那么我们先要求并不需要一一对应,随意对应即可,最后再减掉不合法的方案,这样就可以用容斥来解决。
怎么容斥呢?无非是考虑没有一一对应的关系,那么我们先暴力枚举一下哪些点在图上可以和树上的点进行对应,其他的点不能够和树上的点进行匹配。
那么考虑 dp d p 计算方案数。
f[i][j] f [ i ] [ j ] 表示当前以 i i 为根的子树(假装以1号点为根节点的有根树),并且 i i 在图上对应的点是j的方案数。
每次暴力选择一个和当前 i i 匹配的点,然后再暴力找到这个点在图中的所有儿子,并且用子树进行转移,这样dp一次的复杂度是 O(n×n×n) O ( n × n × n ) ,即树上每个点都要做一次,要暴力枚举和哪个点进行匹配,还需要暴力枚举儿子是哪个点,当然这样肯定不满。
再加上暴力枚举可以进行匹配的点集的枚举,
所以总的时间复杂度是 O(n32n) O ( n 3 2 n )

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define ll long long
#define MAX 20
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
struct Line{int v,next;}e1[MAX*MAX<<1],e2[MAX<<1];
int h1[MAX],h2[MAX];
int cnt1=1,cnt2=1;
int n,m,cnt,a[MAX];
inline void Add1(int u,int v){e1[cnt1]=(Line){v,h1[u]};h1[u]=cnt1++;}
inline void Add2(int u,int v){e2[cnt2]=(Line){v,h2[u]};h2[u]=cnt2++;}
ll f[MAX][MAX],ans=0,num;
void dfs(int u,int ff)
{
    for(int i=h2[u];i;i=e2[i].next)
        if(e2[i].v!=ff)dfs(e2[i].v,u);
    for(int i=1;i<=cnt;++i)//枚举当前点得到匹配点
    {
        f[u][a[i]]=1;
        for(int j=h2[u];j;j=e2[j].next)
            if(e2[j].v!=ff)
            {
                num=0;
                for(int k=h1[a[i]];k;k=e1[k].next)num+=f[e2[j].v][e1[k].v];
                f[u][a[i]]*=num;
                if(!f[u][a[i]])break;
            }
    }
}
int main()
{
    n=read();m=read();
    for(int i=1,u,v;i<=m;++i)u=read(),v=read(),Add1(u,v),Add1(v,u);
    for(int i=2,u,v;i<=n;++i)u=read(),v=read(),Add2(u,v),Add2(v,u);
    for(int i=1;i<(1<<n);++i)//枚举可以进行匹配的点集
    {
        cnt=0;memset(f,0,sizeof(f));
        for(int j=0;j<n;++j)if(i&(1<<j))a[++cnt]=j+1;
        dfs(1,0);num=0;
        for(int j=1;j<=cnt;++j)num+=f[1][a[j]];
        if((n-cnt)&1)ans-=num;else ans+=num;
    }
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值