hihoCoder #1646 : Rikka with String II(容斥原理)

题意

给你 n01S ,其中有些位置可能为 ? 表示能任意填 0/1 。问对于所有填法,把所有串插入到 Trie 的节点数之和(空串看做根节点)。

n20,1|Si|50

题解

直接算显然不太好算的。

Trie 的节点数其实就是本质不同的前缀个数,可以看做 n 个串的所有前缀的并集的大小。

我们可以套用容斥原理最初的式子。

|i=1nAi|=k=1n(1)k11i1<i2<<ikn|Ai1Ai2Aik|

这样的话,我们就可以转化成对于每个子集的交集了,也就是公共前缀的个数。

我们设 f(S)S 集合内的所有子串对于 所有填的方案 的公共前缀的个数。

那么答案为 ans=ST(1)|S|1f(S)

如何得到呢?由于 n 很小我们可以暴力枚举集合,然后枚举当前前缀的长度,直接计数。

  1. 如果当前所有的都是 ? 那么意味着可以任意填 0/1
  2. 如果存在一种数字,其他都是 ? ,那么意味着只能填这种数字。
  3. 如果存在两种数字,那么之后都不可能为公共前缀了,直接退出即可。

直接实现是 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__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10241690.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(238)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 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】
点击右上角即可分享
微信分享提示