HDU 6151 Party(状压DP+Hall定理)

本文介绍了一种利用二部图匹配理论和状态压缩动态规划(状压DP)来解决特定组合计数问题的方法。针对给定的二部图,通过一系列查询找出符合条件的非空子集数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给出一个二部图,左边n个点,标记分别为a1,a2,...,an,右边m个点,标记分别为b1,b2,...,bm,给出一个n×m的矩阵表示二部图的边,1表示连边0表示不连边,q次查询,每次查询给出一个t,在标记被t整除的点中选取一个非空子集,使得该子集中每一个点都属于原图中一条边,且这些边不相交,问满足条件的子集数

Input

第一行输入一整数T表示用例组数,每组用例首先输入三个整数n,m,q表示两边点的数量和查询数,之后输入一个n×m01矩阵表示该二分图的边,然后输入n个整数a1,a2,...,an表示左边点的编号,然后输入m个整数b1,b2,...,bm表示右边点的编号,最后输入q个整数ti表示查询

(1T100,1n,m20,1q10,1ai,bi,ti109)

Output

对于每次查询,输出满足条件的子集数量

Sample Input

1
3 3 2
010
111
010
4 2 6
8 12 5
2 3

Sample Output

Case #1: 23 3

Solution

根据广义Hall引理,只需要从左边选取一个子集X使得其被一个覆盖盖住,从右边选取一个子集Y使得其被一个覆盖盖住,那么这两个子集组成的子集V=X+Y就可以被一个覆盖盖住,根据Hall定理,一个二分图中,一侧的点集X被一个覆盖盖住当且仅当X中任意k个点至少与另一侧的k个点相邻,用状压DP解决这个问题,预处理左侧点连向右侧点的状态L[i],和右侧点连向左侧点的状态R[i],对于一个查询t,求出两侧所有编号被t整除的点的状态A,B,下面只说求A的合法子集个数,即枚举A的一个子集s,假设其中有nums个点,要从B中找到至少nums个点与这些点相邻,以dp[s]表示取A的子集s是否满足条件,首先如果s的一个真子集不满足条件那么dp[s]=0, 如果s的真子集都满足条件,那么只需判断右侧B中是否有nums个点与s相邻,这个通过R[i]记录的状态可以判断,如果B侧第i个点和s状态中某一点相邻,即R[i]&s0则说明s满足条件,以此统计出A中合法子集的数量ansl,同理求出B中合法子集的数量ansr,答案即为anslansr1,减一是减去两边都是空集的情况

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f,maxn=(1<<20)+5;
int T,n,m,q,a[22],b[22],L[maxn],R[maxn],dp[maxn];
char s[22];
int Solve(int *S,int n,int m,int A,int B)
{
    int ans=0,N=1<<n;
    for(int s=0;s<N;s++)
    {
        if((A&s)!=s)continue;
        dp[s]=1;
        int numa=0;
        for(int i=0;i<n;i++)
            if(s&(1<<i))
            {
                numa++,dp[s]&=dp[s^(1<<i)];
                if(!dp[s])break;
            }
        if(!dp[s])continue;
        int numb=0;
        for(int i=0;i<m;i++)
            if((B&(1<<i))&&(S[i]&s))numb++;
        if(numb<numa)dp[s]=0;
        if(dp[s])ans++;
    }
    return ans;
}
int main()
{
    int Case=1;
    scanf("%d",&T);
    while(T--)
    {
        memset(L,0,sizeof(L));
        memset(R,0,sizeof(R));
        scanf("%d%d%d",&n,&m,&q);
        for(int i=0;i<n;i++)
        {
            scanf("%s",s);
            for(int j=0;j<m;j++)
                if(s[j]=='1')L[i]|=(1<<j),R[j]|=(1<<i);
        }
        for(int i=0;i<n;i++)scanf("%d",&a[i]);
        for(int i=0;i<m;i++)scanf("%d",&b[i]);
        printf("Case #%d:",Case++);
        while(q--)
        {
            int t,A=0,B=0;
            scanf("%d",&t);
            for(int i=0;i<n;i++)
                if(a[i]%t==0)A|=(1<<i);
            for(int i=0;i<m;i++)
                if(b[i]%t==0)B|=(1<<i);
            int ansl=Solve(R,n,m,A,B);
            int ansr=Solve(L,m,n,B,A);
            printf(" %I64d",(ll)ansl*ansr-1);
        }
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值