一.題目鏈接:
膜法記錄
二.題目大意:
中文題~~
三.分析:
由於 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;
}