HDU 6151 Party (二分图)

Problem

被含糊的题意坑到了,竟然都没想到是原题(变体? CERC 2016 B)

派对预选受邀人可视作二分图,n 个男孩与 m 个女孩分属两个不相交集合。每人都有一个标记 a1,a2,,an b1,b2,,bm n×m 的 01 矩阵,表示二分图的中的边(0 为不存在这条边,1 为存在)。

有 q 个询问,每个询问给定一个 g ,问在所有标号为 g 的倍数的点组成的子图中,有多少中取点方式,使得选择的点至少是一条边的某个端点,同时不存在任意两条边共享一个端点。

Idea

霍尔定理:二分图 G 中的两不相交点集 X, Y ,X={X1, X2, X3, …, Xn} , Y = {Y1, Y2, Y3, …, Ym} ,G 中有一组无公共点的边,一端恰好为组成 X 的点的充要条件为: X 中的任意 k 个点至少与 Y 中的 k 个点相邻。

记录输入的所有信息,mask_right 表示与右部点 i 相邻的所有左部点的状压状态, mask_left 类似。

问题可以视作为:

ansLeft = 有多少个不同的左部点集合 X ,使得 X 中的任意 k 个点至少与右部中 k 个点相邻。

ansRight = 有多少个不同的右部点集合 Y ,使得 Y 中的任意 k 个点至少与左部中 k 个点相邻。

ans = ansLeft * ansRight - 1 (ansLeft 和 ansRight 中都包含空集,答案不允许空集)。

Code

// 部分代码优化来自于 CERC-2016 B 的官方标程
// 虽然还是在 HDOJ 上跑了 1248 ms .
#include<bits/stdc++.h>
using namespace std;
const int N = 20, M = 20;
int T, n, m, q, g;
bool A[N], B[M];
int a[N], b[M];
char grid[N][M];
int mask_right[M];
int mask_left[N];
int solve(int* mask, int n, int m, bool *A, bool *B) {
    static int can[1<<N];
    int ans = 0;

    int STD = 0;
    for(int i=0;i<n;i++)
        if(A[i] == true)    STD |= (1<<i);
    can[0] = true;
    for(int state=0;state<(1<<n);state++)
    {
        if((STD | state) != STD)    continue;
        int pcnt = 0;
        can[state] = true;
        for(int i=0;i<n;i++)
            if(A[i] && (state & (1<<i)))    can[state] &= can[state-(1<<i)];
        for(int i=0;i<n;i++)
            if(A[i] && (state & (1<<i)))
                ++pcnt;

        int c = 0;
        for(int j=0;j<m;j++)
            if(B[j] && (mask[j] & state))   ++c;

        if(c < pcnt)    can[state] = false;
        if(can[state])  ans++;
    }
    return ans;
}   
int main()
{
    scanf("%d", &T);
    for(int ica=1;ica<=T && scanf("%d %d %d", &n, &m, &q)!=EOF;ica++)
    {
        memset(mask_left, 0, sizeof(mask_left));
        memset(mask_right, 0, sizeof(mask_right));
        for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
        {
            scanf(" %c", &grid[i][j]);
            grid[i][j] -= '0';
            if(grid[i][j])  
                mask_right[j] |= 1<<i,
                mask_left[i] |= 1<<j;
        }
        for(int i=0;i<n;i++)
            scanf("%d", &a[i]);
        for(int j=0;j<m;j++)
            scanf("%d", &b[j]);

        printf("Case #%d: ", ica);
        while(q-- && scanf("%d", &g))
        {
            for(int i=0;i<n;i++)
                if(a[i] % g == 0)   A[i] = 1;
                else    A[i] = 0;
            for(int j=0;j<m;j++)
                if(b[j] % g == 0)   B[j] = 1;
                else    B[j] = 0;

            long long ansLeft = solve(mask_right, n, m, A, B);
            long long ansRight = solve(mask_left, m, n, B, A);
            printf("%lld%c", ansLeft * ansRight - 1, q?' ':'\n');
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值