膜法记录(牛客小白月赛23 A,子集前缀和)

一.题目链接:

膜法记录

二.题目大意:

中文题~~

三.分析:

由于 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 后,零列个数便为 \sum_{j}cnt2[j],其中 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;
}

猜你喜欢

转载自blog.csdn.net/The___Flash/article/details/105026057