Source: S o u r c e : 2016-2017 ACM-ICPC, Central Europe Regional Contest (CERC 16)
Problem: P r o b l e m : 给你一个二分图,每个点都有点权,问这个图有多少个子集,满足点权之和不小于给定值 t t ,并且被某个匹配覆盖。
容易证明若
X
X
集存在子集属于某一匹配,
Y
Y
集存在子集属于某一匹配,那么
A
A
,一定同属于某一匹配,这样就可以左右拆开来做了。
而判定子集是否合法,可以用到Hall定理。
Hall定理:
若二分图存在完美匹配,且大小为
n
n
,那么取任意,均满足
X
X
集选出个不同的点,它们连向
Y
Y
集的点的个数不小于。
Code: C o d e :
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ALL(A) (A).begin(), (A).end()
#define CLR(A, X) memset(A, X, sizeof(A))
typedef long long LL;
typedef pair<int, int> PII;
const double eps = 1e-10;
const double PI = acos(-1.0);
const auto INF = 0x3f3f3f3f;
int dcmp(double x) { if(fabs(x) < eps) return 0; return x<0?-1:1; }
const int MAXN = 20;
int lt[1<<20], rt[1<<20], ok[1<<20], cnt[1<<20];
int a[20], b[20];
vector<int> g1, g2;
void update(int *A, int *C, int n, vector<int> &g) {
for(int S = 0; S < (1<<n); S++) {
int sum = 0;
ok[S] = 1;
for(int i = 0; i < n; i++) if(S & (1<<i)) {
A[S] |= A[S^(1<<i)];
ok[S] &= ok[S^(1<<i)];
sum += C[i];
}
if(ok[S] && cnt[A[S]]>=cnt[S]) g.pb(sum);
else ok[S] = 0;
}
}
int main() {
int n, m, x;
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
scanf("%1d", &x);
if(x) {
lt[1<<i] |= 1<<j;
rt[1<<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]);
}
int nm = max(n, m);
for(int S = 0; S < (1<<nm); S++) {
cnt[S] = cnt[S>>1]+(S&1);
}
update(lt, a, n, g1);
update(rt, b, m, g2);
scanf("%d", &x);
sort(ALL(g1));
LL ans = 0;
for(int y:g2) {
int t = lower_bound(ALL(g1), x-y)-g1.begin();
ans += g1.size()-t;
}
printf("%lld\n", ans);
return 0;
}