题意
给你 n 个 01 串 S ,其中有些位置可能为 ? 表示能任意填 0/1 。问对于所有填法,把所有串插入到 Trie 的节点数之和(空串看做根节点)。
n≤20,1≤|Si|≤50
题解
直接算显然不太好算的。
Trie 的节点数其实就是本质不同的前缀个数,可以看做 n 个串的所有前缀的并集的大小。
我们可以套用容斥原理最初的式子。
∣∣
∣∣n⋃i=1Ai∣∣
∣∣=n∑k=1(−1)k−1∑1≤i1<i2<⋯<ik≤n|Ai1∩Ai2∩⋯∩Aik|
这样的话,我们就可以转化成对于每个子集的交集了,也就是公共前缀的个数。
我们设 f(S) 为 S 集合内的所有子串对于 所有填的方案 的公共前缀的个数。
那么答案为 ans=∑S⊆T(−1)|S|−1f(S)
如何得到呢?由于 n 很小我们可以暴力枚举集合,然后枚举当前前缀的长度,直接计数。
- 如果当前所有的都是 ? 那么意味着可以任意填 0/1 。
- 如果存在一种数字,其他都是 ? ,那么意味着只能填这种数字。
- 如果存在两种数字,那么之后都不可能为公共前缀了,直接退出即可。
直接实现是 O(2nn|S|) 的。可以把状态集合合并一下优化到 O(2n|S|) 。(但是我太懒了)
代码
实现的时候不要忘记是所有填的方案。
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifdef zjp_shadow
freopen ("1646.in", "r", stdin);
freopen ("1646.out", "w", stdout);
#endif
}
const int N = 21, L = 51, Mod = 998244353;
int n, len[1 << N], Pow[N * L];
char str[N][L];
int main () {
File();
n = read();
Set(len, 0x3f);
int tot = 0;
Rep (i, n) {
scanf ("%s", str[i] + 1);
len[1 << i] = strlen(str[i] + 1);
For (j, 1, strlen(str[i] + 1))
if (str[i][j] == '?') ++ tot;
}
Pow[0] = 1;
For (i, 1, tot)
Pow[i] = 2ll * Pow[i - 1] % Mod;
Rep (i, 1 << n)
chkmin(len[i], min(len[i ^ (i & -i)], len[i & -i]));
int ans = 0;
Rep (i, 1 << n) if (i) {
int res = 0, sum = tot, pre = 0;
For (j, 1, len[i]) {
int flag = 0, now = 0;
Rep (k, n) if (i >> k & 1) {
if (str[k][j] == '?') ++ now;
else flag |= (str[k][j] - '0' + 1);
}
sum -= now;
if (flag == 3) break;
if (!flag) ++ pre;
res = (res + Pow[pre + sum]) % Mod;
}
ans = (ans + (__builtin_popcount(i) & 1 ? 1 : -1) * res) % Mod;
}
ans += Pow[tot]; if (ans < 0) ans += Mod;
printf ("%d\n", ans);
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】