NOI模拟:保镖(Hall定理)

来源:互联网 发布:剑灵灵族捏脸数据截图 编辑:程序博客网 时间:2024/04/29 22:25

题意:
给一个二分图,找出满足以下条件的点集的个数:
1.该点集是原点集的子集。
2.该点集是一个匹配的点集的子集。
(n ≤ 20)

题解:
首先任意匹配的点集一定是一个最大匹配的子集。
题意转化为找出是最大匹配的子集的点集个数。

由Hall定理,对于一边的点而言,如果一个点集的任意子集满足在一个最大匹配中,而且这个点集满足连接的点≥这个点集的大小,那么这个点集一定在最大匹配中。

同理,对于两边的点而言,一样满足:
把两边选出的点染色,并选出两边的点连出的边,不难发现这些边最终会组成环和链,对于一个环,两两匹配即可,对于一条链,首尾依次匹配即可。

#include <bits/stdc++.h>using namespace std;const int Maxn = 2e6 + 50;int n, m, wa[25], wb[25], t, lima, limb, vala[Maxn], cnta, valb[Maxn], cntb, status[2][25];bool isa[Maxn], isb[Maxn];char ch[25][25];inline int findpos(int x){    int l = 1, r = cntb, ans = 0;    while (l <= r)    {        int mid = (l + r) >> 1;        if (valb[mid] >= x)            ans = (cntb - mid + 1), r = mid - 1;        else            l = mid + 1;    }    return ans;}inline int check(int lim, int x){    int res = 0;    for (int j = 0; j < lim; j++)    {        if (x & (1 << j))            ++res;    }    return res;}int main(){    freopen("lx.in", "r", stdin);    scanf("%d%d", &n, &m);    for (int i = 0; i < n; i++)    {        scanf("%s", ch[i]);        for (int j = 0; j < m; j++)        {            if (ch[i][j] == '1')            {                status[0][i] |= (1 << j);                status[1][j] |= (1 << i);            }        }    }    for (int i = 0; i < n; i++)    {        scanf("%d", &wa[i]);        for (int j = 0; j < m; j++)            if (ch[i][j] == '1')            {                isa[(1 << i)] = 1;                break;            }    }    for (int i = 0; i < m; i++)    {        scanf("%d", &wb[i]);        for (int j = 0; j < n; j++)            if (ch[j][i] == '1')            {                isb[(1 << i)] = 1;                break;            }    }    scanf("%d", &t);    lima = (1 << n) - 1;    limb = (1 << m) - 1;    for (int i = 1; i <= lima; i++)    {        int sum = 0, cnt = 0, bz = 1, Status = 0;        for (int j = 0; j < n; j++)        {            if (i & (1 << j))            {                ++cnt;                Status |= status[0][j];                if (!isa[i ^ (1 << j)])                    bz = 0;                sum += wa[j];            }        }        if (cnt == 1 && isa[i])            vala[++cnta] = sum;        if (cnt > 1 && cnt <= m && bz && check(m, Status) >= cnt)            vala[++cnta] = sum, isa[i] = 1;    }    for (int i = 1; i <= limb; i++)    {        int sum = 0, cnt = 0, bz = 1, Status = 0;        for (int j = 0; j < m; j++)        {            if (i & (1 << j))            {                ++cnt;                Status |= status[1][j];                if (!isb[i ^ (1 << j)])                    bz = 0;                sum += wb[j];            }        }        if (cnt == 1 && isb[i])            valb[++cntb] = sum;        if (cnt > 1 && cnt <= n && bz && check(n, Status) >= cnt)            valb[++cntb] = sum, isb[i] = 1;    }    vala[++cnta] = 0;    valb[++cntb] = 0;    sort(valb + 1, valb + cntb + 1);    long long ans = 0;    for (int i = 1; i <= cnta; ++i)    {        ans += findpos(t - vala[i]);    }    printf("%lld\n", ans);}