[BZOJ1879][SDOI2009]Bill的挑战
题目描述
Input
本题包含多组数据。
第一行:一个整数\(T\),表示数据的个数。
对于每组数据:
第一行:两个整数,\(N\)和\(K\)(含义如题目表述)。
接下来\(N\)行:每行一个字符串。
对于\(100\%\)的数据,\(T ≤ 5,M ≤ 15\),字符串长度\(≤ 50\)。
Output
若存在输出\(YES\),否则输出\(NO\)。
Sample Input
5
3 3
???r???
???????
???????
3 4
???????
?????a?
???????
3 3
???????
?a??j??
????aa?
3 2
a??????
???????
???????
3 2
???????
???a???
????a??
Sample Output
914852
0
0
871234
67018
其实看到那么小的数据范围那解题方法也就只有状压或者容斥了。
鉴于网上状压的题解过多,我们来介绍一下容斥的写法。
很显然,我们对于一个状态我们要求出有多少种符合条件的串。
怎么写呢?
对于当前状态\(i\),长度为\(len\),我们逐位考虑,对于当前为\(j\)。
对于每个在当前状态的串。
1.若出现有两个串的\(Sx[j]!=?,Sy[j]!=?,Sx[j]!=Sy[j]\)则说明不存在符合条件的串。
2.若出现该位上所有串都为\(?\),即对于所有\(k\in i\)都有\(S_k[j]=?\),则该位上随便选,则方案数乘\(26\)。
3.否则总方案数不变。
然后,我们发现若直接这样写,会出现重复的一部分我们没减掉。
就是会出现一个串在状态\(i\)可行,但在状态\(j\)也可行,所以我们要减掉。
我们倒着枚举状态,对于当前\(i\),\(i\)的子集必定会包含\(i\)的可行状态,所以我们只用把\(i\)的子集减去\(i\)的方案数即可。
最后,我们把所有刚好有\(k\)个\(1\)的状态的\(dp\)累加到答案上即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define abs(a) ((a)<0?-(a):(a))
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(3,"Ofast","inline")
inline int Read(void) {
int res = 0, f = 1;
char c;
while (c = getchar(), c < 48 || c > 57)if (c == '-')f = 0;
do res = (res << 3) + (res << 1) + (c ^ 48);
while (c = getchar(), c >= 48 && c <= 57);
return f ? res : -res;
}
template<class T>inline bool Min(T &a, T const&b) {return a > b ? a = b, 1 : 0;}
template<class T>inline bool Max(T &a, T const&b) {return a < b ? a = b, 1 : 0;}
const int N = 20, M = (1 << 16) + 5, mod = 1e6 + 3;
bool MOP1;
int dp[M], Sz[M];
char S[N << 2][N << 2];
inline void Solve(void) {
int n = Read(), K = Read(), Ans = 0;
ret(i, 0, n)scanf("%s", S[i] + 1);
int len = strlen(S[1] + 1);
clr(dp, 0);
drep(i, (1 << n) - 1, 0) {
if (Sz[i] < K)continue;
int res = 1;
rep(j, 1, len) {
int flag = 1, pos = -1;
ret(k, 0, n)if (i & 1 << k) {
if (S[k][j] == '?')continue;
if (pos == -1) {
pos = S[k][j] - 'a';
continue;
}
if (S[k][j] - 'a' != pos) {
flag = 0;
break;
}
}
if (flag && pos == -1)res *= 26, res %= mod;
if (!flag)res = 0;
}
dp[i] += res, Mod(dp[i]);
if (Sz[i] == K)Ans += dp[i], Mod(Ans);
for (int j = i & (i - 1); j; j = i & (j - 1)) {
dp[j] += mod - dp[i], Mod(dp[j]);
}
}
printf("%lld\n", Ans);
}
bool MOP2;
void _main(void) {
int T = Read();
ret(i, 1, M)Sz[i] = Sz[i & (i - 1)] + 1;
while (T--)Solve();
}
signed main() {
_main();
return 0;
}