A
思路:
因为行的范围比较小,列的范围比较大,所以可以
枚举行的选择方式,然后在判断是否可行。复杂度
数据太水了
char w[50][N];
int x[N],y[N];
int _x[N],_y[N];
int main(){
int t = read();
while(t--){
int n = read(),m = read(),a = read(),b = read();
int S = (1<<n);
rep(i,1,n){
rep(i,1,m){
w[i][j] = gc();
if(w[i][j] == '*') x[i] ++,y[j] ++;
}
}
bool r = 1;
rep(p,0,S-1){
if(__builtin_popcount(p) != a) continue;
rep(i,1,n) _x[i] = x[i];rep(i,1,m) _y[i] = y[i];
rep(i,0,21){
if((p>>i)&1) {
_x[i+1] = 0;
rep(j,1,m) if(w[i+1][j] == '*'&&_y[j]) _y[j] --;
}
}
int cnt = 0;
rep(i,1,m) if(y[i]) cnt ++;
if(cnt<=b) {r = 0;break;}
}
if(r) puts("yes");
else puts("no");
}
}
做法
子集前缀和
因为行的范围很小,所以我们可以将每列的敌人压缩为一个二进制位,即
预处理 每列的敌人状态 并统计每个状态的数目,如
,1000代表某一列的状态,第4行有一个敌人,并且
,说明这样的列有两个,那么我们消灭第四行,就会有两列敌人的个数变为0,那么对于
是否也如上面例子所说呢,不是的,因为
包含了
这两种情况,也就是说
。这也就是预处理 自己前缀和的目的。
预处理完后,我们遍历
这些状态,如果
中1的个数小于等于
并且
就说明这个可以消除所有列。
很棒
char w[22][N];
unordered_map<int,int> cnt;
int main(){
int t = read();
while(t--){
cnt.clear();
int n = read(),m = read(),a = read(),b = read();
rep(i,1,n){
rep(j,1,m){
cin >> w[i][j];
}
}
rep(i,1,m){
int p = 0;
rep(j,1,n){
if(w[j][i] == '*') p|= 1<<(j-1);
}
cnt[p] ++;
}
for(int i = 1;i <= n;++i){
for(int j = 0;j < (1<<n);++j)
if(((j>>(i-1))&1) == 0) cnt[j|(1<<(i-1))] += cnt[j];
}
bool r = 1;
rep(i,0,(1<<n)-1){
if(__builtin_popcount(i)<=a&&m-cnt[i]<= b) {r = 0;break;}
}
if(r == 0) puts("yes");
else puts("no");
}
}