保镖

Description

蒟蒻 \(YxuanwKeith\) 想成为 \(Philisweng\) 的保镖,但是作为预备队员的保镖智商肯定也不能低,至少要回答出下面这个问题:现在有一副若干条边的二分图,左边有 \(N\) 个点 \(a_i\) ,右边有 \(M\) 个点 \(b_i\) ,每个点都有一个权值 \(w_i\) 。一个合法的子图满足以下两个限制:

  1. 选出的点权和大于等于限制 \(t\)

  2. 并且可以从图中选出若干条边, 使得二分图中每个点最多被一条边覆盖,而选出的点要恰好被一条边覆盖。

求总方案数。由于 \(YxuanwKeith\) 很弱,所以他找到你来回答这个问题。

Input

第一行包括两个数 \(N,M\) ,分别表示左边点的个数和右边点的个数。

\(2\) 行到第 \(N+1\) 行,第 \(i\) 行一个长度为 \(M\) 的字符串 \(S_i\) ,第 \(j\) 个字符如果是 \(0\) 表示左边第 \(i-1\) 个点和右边第 \(j\) 个点没有连边,如果是 \(1\) 表示有连边。

\(N+2\) 行, \(N\) 个非负整数,第 \(i\) 个非负整数表示左边第 \(i\) 个点的权值 \(W_i\)

\(N+3\) 行, \(M\) 个非负整数,第 \(i\) 个非负整数表示右边第 \(i\) 个点的权值 \(v_i\)

\(N+4\) 行,一个正整数 \(t\) ,表示题目中的限制。

Output

输出共一行,一个数,表示答案。

Sample

Sample Input

3 3
010
111
010
1 2 3
8 5 13
21

Sample Output

3

Limit

对于 \(30\%\) 的数据: \(N,M\le 8\)

对于另外 \(30\%\) 的数据:左边的点和右边的点两两间都有连边。

对于 \(100\%\) 的数据: \(N,M\le 20\)\(t\le 4\times 10^8\)\(w_i, v_i\le 10^8\)

Solution

首先讲一下 \(Hall\) 定理:

  • 对于一个二分图具备完备匹配的充要条件是对于左边点的每一个子集,和子集内的点相连的点集大小大于等于子集大小。

用状压的方式找出两边都合法的方案,得到两个数组,用双指针在这两个数组上搞一搞就可以统计答案了。

具体见代码,自认为还是很清晰易懂的。

#include<bits/stdc++.h>
using namespace std;
#define N 21
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define ll long long
int n, m, _n, g[2][1 << N], f[2][1 << N], w[N << 2], rec[2][1 << N], t; ll ans;
char s[N];
void solve(int n, int t) {
	rep(S, 0, (1 << n) - 1) {
		f[t][S] = 1; int SS = 0; ll sum = 0;
		rep(i, 1, n) if ((S >> (i - 1)) & 1) {
			int _S = S ^ (1 << i - 1); if (!f[t][_S]) { f[t][S] = 0; break; }
			SS |= g[t][i], sum += w[t * _n + i];
		}
		if (__builtin_popcount(SS) < __builtin_popcount(S)) f[t][S] = 0;
		if (f[t][S]) rec[t][++rec[t][0]] = sum;
	}
}
int main() {
	freopen("guard.in", "r", stdin); freopen("guard.out", "w", stdout);
	scanf("%d%d", &n, &m); _n = n;
	rep(i, 1, n) {
		scanf("%s", s + 1);
		rep(j, 1, m)  if (s[j] == '1') g[0][i] |= 1 << (j - 1), g[1][j] |= 1 << (i - 1);
	}
	rep(i, 1, n + m) scanf("%d", &w[i]); scanf("%d", &t);
	solve(n, 0), solve(m, 1);
	rep(x, 0, 1) sort(rec[x] + 1, rec[x] + rec[x][0] + 1);
	int q = rec[1][0] + 1;
	rep(p, 1, rec[0][0]) {
		while (q > 1 && rec[1][q - 1] + rec[0][p] >= t) q--;
		if (rec[1][q] + rec[0][p] >= t) ans += rec[1][0] - q + 1;
	}
	printf("%lld", ans);
	return 0;
}
posted @ 2018-02-23 21:04  aziint  阅读(182)  评论(0)    收藏  举报
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.