P2167
P2167 [SDOI2009] Bill的挑战 一种与众不同的做法。
暴力容斥。要求与刚好 \(k\) 个字符串匹配,考虑处理 \(f_s\) 表示与 \(s\) 集合中这些串都匹配的 \(T\) 的个数,则答案:
\[\sum_{s\in\text{U}\ \wedge\ |s|=k\ } \sum_{s \in \text{sub} \in \text{U}} -1^{|sub|-|s|}\cdot f_{sub}
\]
其中 \(U=\{1, 2, 3, \cdots, n\}\)。
\(f_s\) 可以直接暴力,遍历集合中每一个串,考虑每一个位置的方案。也可以记录下对于每个 \(s\) 的每一位的方案然后转移,会快一些。
单组时间复杂度 \(O(2^n\times len+2^k\times(^{k}_{n})\ )\ )\),大概就是非常跑不满的 \(O(2^n\times len+3^n)\)。
上界大概只有 \(\text{2e7}\) 次运算,最大点 56ms 比较快吧还算。
// Problem: P2167
#include<iostream>
#include<stdio.h>
#include<string.h>
#define Int int
typedef long long ll;
#define typ int
int __Mst_p;
const int N = 16, L = 51, S = 32769, mod = 1e6 + 3;
typ read(){typ x=0,f=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}return x*f;}
void print(typ x){if(x<0)putchar('-'),x=-x;if(x>9)print(x/10);putchar(x%10+48);}void print(typ x, char c){print(x);putchar(c);}
char gc(){char c=getchar();while(c==' '||c=='\n')c=getchar();return c;}
int t, n, k, len;
char a[N][L];
int g[S][L], f[S]; // g[s][i]:与 {s} 匹配的串,第 i 位的方案。
int __Med_p;
int main() {
t = read();
while(t--) {
n = read(), k = read();
for(int i = 1; i <= n; i++) scanf(" %s", a[i] + 1);
len = strlen(a[1] + 1);
for(int i = 1; i <= len; i++) g[0][i] = 26;
for(int s = 1; s < (1 << n); s++) { // 2^n
f[s] = 1;
int lst = __builtin_ctz(s) + 1;
for(int i = 1; i <= len; i++) { // len
int tmp = g[s - (s & -s)][i];
if(tmp == 26) {
if(a[lst][i] != '?') g[s][i] = a[lst][i] - 97;
else g[s][i] = 26;
} else if(tmp >= 0) {
if(a[lst][i] == '?' || a[lst][i] - 97 == tmp) g[s][i] = tmp;
else g[s][i] = -1;
} else g[s][i] = -1;
f[s] = f[s] * (g[s][i] == 26 ? 26 : (g[s][i] == -1 ? 0 : 1)) % mod;
}
}
int ans = 0;
for(int s = 1; s < (1 << n); s++) {
if(__builtin_popcount(s) != k) continue;
int Cus = ((1 << n) - 1) ^ s;
for(int set = Cus, sub; ; set = (set - 1) & Cus) {
sub = set | s;
ans = (ans + f[sub] * (__builtin_popcount(set) & 1 ? -1 : 1)) % mod;
if(set == 0) break;
}
}
print((ans + mod) % mod, '\n');
}
return 0;
}