一.题目链接:
膜法记录
二.题目大意:
中文题~~
三.分析:
由于 n 只有 20,考虑二进制枚举操作的行
因此我们只需预处理出对行进行 i 操作后,零列的个数,记为 cnt[i].
先求出列状态为 i 的列的个数,记为 cnt2[i] 中.
那么 cnt[i] = sum(cnt2[i 的子集]).
例如求出列状态数 cnt2[00], cnt2[01], cnt2[10], cnt2[11] 后,那么
cnt[00] = cnt2[00]
cnt[01] = cnt2[00] + cnt2[01]
cnt[10] = cnt2[00] + cnt2[10]
cnt[11] = cnt2[00] + cnt2[01] + cnt2[10] + cnt2[11].
很明显求出 cnt2[] 后,再求子集前缀和即可.
比如输入为:
1 4 4 2 3 *... *... .*** ..**
那么列状态分别为 0011、0100、1100、1100
立即推 cnt2[0011] = 1, cnt2[0100] = 1, cnt2[1100] = 2.
假设对行 3,4 进行操作,那么行操作状态为 1100.
考虑进行行操作 1100 后,哪些列会变为零列,那么 m - 零列个数 便为还需的列操作数.
易知进行行操作 1100 后,列状态为 0000、0100、1000、1100 的列都会变为零列.
因此进行行操作 1100 后,零列个数便为 cnt2[0000] + cnt2[0100] + cnt2[1000] + cnt2[1100].
即进行行操作 i 后,零列个数便为 ,其中 j 为 i 的子集.
四.代码实现:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = (int)1e5;
const int N = (int)2e1;
char s[N + 5][M + 5];
int cnt[1<<N];
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n, m, a, b; scanf("%d %d %d %d", &n, &m, &a, &b);
for(int i = 0; i < (1<<n); ++i) cnt[i] = 0;
for(int i = 0; i < n; ++i) scanf("%s", s[i]);
for(int i = 0; i < m; ++i)
{
int state = 0;
for(int j = 0; j < n; ++j) state |= (s[j][i] == '*' ? (1<<j) : 0);
++cnt[state];
}
for(int i = 0; i < n; ++i)//子集前缀和
{
for(int j = 0; j < (1<<n); ++j) if(!(j & (1<<i))) cnt[j | (1<<i)] += cnt[j];
}
bool flag = 0; for(int i = 0; i < (1<<n); ++i) flag |= (__builtin_popcount(i) <= a && m - cnt[i] <= b);
puts(flag ? "yes" : "no");
}
return 0;
}