F - typewriter
https://atcoder.jp/contests/abc246/tasks/abc246_f
首先我们根据容斥原理公式:(摘自百度百科):
∣
A
1
∪
A
2
∪
.
.
.
∪
A
m
∣
=
|A_1\cup A_2\cup...\cup A_m|=
∣A1∪A2∪...∪Am∣=
∑
1
≤
i
≤
m
∣
A
i
∣
−
∑
1
≤
i
≤
j
≤
m
∣
A
i
∩
A
j
∣
+
∑
1
≤
i
≤
j
≤
k
≤
m
∣
A
i
∩
A
j
∩
A
k
∣
−
.
.
.
+
(
−
1
)
m
−
1
∣
A
1
∩
A
2
∩
.
.
.
∩
A
m
∣
\sum\limits_{1\leq i\leq m}|A_i|-\sum\limits_{1\leq i\leq j\leq m}|A_i\cap A_j|+\sum\limits_{1\leq i\leq j\leq k\leq m}|A_i\cap A_j\cap A_k|-...+(-1)^{m-1}|A_1\cap A_2\cap...\cap A_m|
1≤i≤m∑∣Ai∣−1≤i≤j≤m∑∣Ai∩Aj∣+1≤i≤j≤k≤m∑∣Ai∩Aj∩Ak∣−...+(−1)m−1∣A1∩A2∩...∩Am∣
那么对于这题来说我们可以状压枚举 2 N − 1 2^N-1 2N−1 个状态,这 2 N − 1 2^N-1 2N−1 个状态其实就一一对应了上述公式中某一个交集。那么这个状态对答案具体贡献的值是多少呢?首先根据这个状态中 1 1 1 的个数可用判断其正负号,然后我们就要求其对应交集中元素个数,几个字符串的交集的元素个数就是他们共有的字母在 L L L 的长度上随便放产生的串的个数,也就是 ( 共 有 的 字 母 ) L (共有的字母)^L (共有的字母)L 。那么共有的字符有多少呢?我们把字符串状压后求 按位交 即可。
这里求非负 i n t int int 型的二进制个数可用函数__builtin_popcount(),第二次见到这个函数,起初觉得太长,不好记,不如手写,后面发现这个函数底层实现的效率真的高,比手写的要好很多,复杂度几乎是常数而非 l o g log log 的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int mod = 998244353;
int n, L;
char s[N];
int v[30];
int ksm(int base, int n) {
int ans = 1;
while(n) {
if (n & 1) ans = 1ll * ans * base % mod;
base = 1ll * base * base % mod;
n >>= 1;
}
return ans;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
scanf("%d%d", &n, &L);
for (int i = 0; i < n; ++i) {
scanf("%s", (s + 1));
int len = strlen(s + 1);
for (int j = 1; j <= len; ++j) {
v[i] |= (1 << (s[j] - 'a'));
}
}
ll ans = 0;
for (int i = 1; i < (1 << n); ++i) {
int cur = (1 << 26) - 1;
for (int j = 0; j < n; ++j) {
if (i & (1 << j)) cur &= v[j];
}
if (__builtin_popcount(i) & 1) {
ans = (ans + ksm(__builtin_popcount(cur), L)) % mod;
}
else {
ans = (ans - ksm(__builtin_popcount(cur), L) + mod) % mod;
}
}
printf("%d", ans);
}