视频加载失败

安吉D17 T4

2808 字
14 分钟
安吉D17 T4
原题呈现

P7670 毒蛇越狱 / Snake Escaping#

题目描述#

JOI 实验室有 2L2^L 条毒蛇.蛇的编号为 0,1,,2L10,1,\cdots,2^L−1.每条蛇从头到尾分为 LL 个部分.每个部分的颜色是蓝色或红色. 对于毒蛇 ii,令 i=k=1Lck2Lki = \sum_{k=1}^{L} c_k2^{L-k}0ck10 \leq c_k \leq 1)为 ii 的二进制表达式.那么,

  • 如果 ck=0c_k=0,毒蛇 ii 从头开始的第 kk 部分的颜色是蓝色,
  • 如果 ck=1c_k=1,毒蛇 ii 从头开始的第 kk 部分的颜色是红色.

每条毒蛇都有一个 0099 之间的整数,包括 0099,为毒性.给出一个由 0,1,2,3,4,5,6,7,8,9\texttt{0,1,2,3,4,5,6,7,8,9} 组成的长度为 2L2^L 的字符串 SS.第 ii 个字符(1i2L1 \leq i \leq 2^L)是毒蛇 i1i−1 的毒性.由于毒蛇行动迅速,所以经常从 JOI 实验室逃走.住在实验室附近的人向 JOI 实验室投诉,他们看到毒蛇从实验室逃逸.您将收到 QQ 天的投诉清单. 第 dd 天的投诉(1dQ1 \leq d \leq Q)是一个长度为 LL 的字符串 TdT_d,由 0,1,?\texttt{0,1,?} 组成.

  • 如果 TdT_d 的第 jj 个字符(1jL1 \leq j ≤ L)为 0\texttt{0},这意味着第 dd 天从实验室逃出的每条毒蛇的第 jj 个部分是蓝色的,
  • 如果 TdT_d 的第 jj 个字符(1jL1 \leq j \leq L)为 1\texttt{1},这意味着第 dd 天从实验室逃出的每条毒蛇的第 jj 部分是红色的,并且
  • 如果 TdT_d 的第 jj 个字符(1jL1 \leq j \leq L)为 ?\texttt{?},这意味着人们没有提供关于第 dd 天从实验室逃逸的毒蛇的第 jj 部分的信息.

所有的投诉都是准确的信息.所有从实验室逃逸的毒蛇都在同一天被 JOI 实验室的工作人员收留.可能发生同一条蛇在不同的日子逃脱. JOI 实验室执行主任 K 教授为了估计毒蛇逃逸的风险,想知道可能逃出实验室的毒蛇的毒性总和. 你的任务是编写一个程序,根据 QQ 天的投诉列表,计算每天可能从实验室逃逸的蛇的毒性总和. 现给定描述毒蛇毒性的字符串 SSQQ 天的投诉列表,请编写一个程序来计算每天可能从实验室逃逸的蛇的毒性总和.

输入格式#

第一行包含两个空格分隔的整数 LLQQ,分别是每条毒蛇的部位数和投诉天数.第二行包含长度为 2L2^L 的字符串 SS,描述了毒蛇的毒性.后面 QQ 行的第 dd 行包含一个长度为 LL 的字符串 TdT_d,为第 dd 天的投诉.

输出格式#

QQ 行,第 dd 行应包含一个整数,即第 dd 天可能从实验室逃逸的蛇的毒性总和.

输入输出样例 #1#

输入 #1#

3 5
12345678
000
0??
1?0
?11
???

输出 #1#

1
10
12
12
36

输入输出样例 #2#

输入 #2#

4 8
3141592653589793
0101
?01?
??1?
?0??
1?00
01?1
??10
????

输出 #2#

9
18
38
30
14
15
20
80

说明/提示#

数据规模与约定#

对于 100%100 \% 的数据,1L201 \leq L \leq 201Q1061 \leq Q \leq 10^6SS 是长度为 2L2^L 的字符串,字符串 SS0,1,2,3,4,5,6,7,8,9\texttt{0,1,2,3,4,5,6,7,8,9} 组成,TdT_d 是长度为 LL1dQ1 \leq d \leq Q)的字符串,字符串 TdT_d0,1,?\texttt{0,1,?}1dQ1 \leq d \leq Q)组成.

  • Subtask 1155 points):L10L \leq 10Q1000Q \leq 1000
  • Subtask 2277 points):L10L \leq 10
  • Subtask 331010 points):L13L \leq 13
  • Subtask 445353 points):Q5000Q \leq 5000
  • Subtask 552525 points):没有额外的限制.

样例说明#

对于样例 11L=3L=3,共 23=82^3=8 条毒蛇,它们中的每一条都分为 33 个部分. 投诉时间为 55 天.

  • 第一天,可能逃出实验室的毒蛇只有毒蛇 00.毒性总和为 11
  • 第二天,可能从实验室逃逸的毒蛇是毒蛇 0,1,2,30,1,2,3.毒性总和为 1010
  • 第三天,可能从实验室逃逸的毒蛇是毒蛇 4,64,6.毒性总和为 1212
  • 第四天,可能从实验室逃逸的毒蛇是毒蛇 3,73,7.毒性总和是 1212
  • 第五天,可能从实验室逃逸的毒蛇是毒蛇 0,1,2,3,4,5,6,70,1,2,3,4,5,6,7.毒性总和为 3636

此题题意就是给定一个模式串,求二进制表示匹配这个模式串的所有数权值之和.

最容易想到的就是枚举所有问号部分应该填什么,然后将所有可能的数字权值加起来.下面的程序实现了这个暴力.其中,

  • A 的二进制上每一位是 1,当且仅当模式串上这一位是 0
  • B 的二进制上每一位是 1,当且仅当模式串上这一位是 1
  • C 的二进制上每一位是 1,当且仅当模式串上这一位是 ?
// 枚举 T,T 表示我们钦定这些部分应该填 1
for (int T = C; T > 0; T = (T - 1) & C) {
int U = B | T;
ans += w[U];
}
ans += w[B];

程序的复杂度高达 O(q2L)O(q2^L),需要优化.

在复杂度中,LL 在指数位上,只需要让 L8L\leq 8 就可以了.因此,对于问号数量小于等于 8 的模式串,可以使用上面的暴力.那对于问号数量大于 8 的呢?问号数量既然大于 8,由于 L20L\leq 20,那么必然会有 1 的数量小于 8,或者 0 的数量小于 8.对于这两种情况分别考虑.

对于 0 的数量小于 8 的情况,我们可以尝试使用容斥.具体来说,对于每一个为 0 的位置,设其组成一个集合,即为上述的 AA,则满足条件的情况为:模式串中每一个规定为 0 的位置都全部遵守的权值,减去钦定有一个位置不遵守的权值,加上有钦定两个位置不遵守的权值……公式化的,设钦定模式串中为 0 的位置必须为 1 的位置集合是 TT,则有:

ans=TA((1)Tg(BT))ans=\sum_{T\subseteq A}((-1)^{|T|}g(B\cup T))

其中,BB 的含义为上文所述,故 BTB\cup T 的含义为当前钦定限制下,所有必须为 1 的位置集合,g(x)g(x) 表示超集和,即所有满足以下条件的数字 yy权值和

  • 若设 aa 的从右到左第 ii 个二进制位为 aia_i,则当 xi=1x_i=1 时,yi=1y_i=1.即 xx 中为 1 的位 yy 中也必须为 1.

公式化的,其统计的是 Txw(T)\sum_{T\supseteq x}w(T)

g(x)g(x) 可以通过高维后缀和预处理出来,在后文会详细说明.

// 枚举 T,T 表示我们钦定这些部分应该填 1,即使模式串中为 0
for (int T = A; T > 0; T = (T - 1) & A) {
int U = B | T;
if (__builtin_popcount(T) & 1) {
ans -= g[U];
} else {
ans += g[U];
}
}
ans += g[B];

对于 1 的数量小于 8 的情况,我们可以仍可以像处理 0 一样使用容斥.具体来说,对于每一个为 1 的位置,设其组成一个集合,即为上述的 BB,则满足条件的情况为:模式串中每一个规定为 1 的位置都全部遵守的权值,减去钦定有一个位置不遵守的权值,加上有钦定两个位置不遵守的权值……公式化的,设钦定模式串中为 1 的位置必须为 0 的位置集合是 TT,则有:

ans=TB[(1)Tf((AT)c)]ans=\sum_{T\subseteq B}[(-1)^{|T|}f((A\cup T)^c)]

其中,ATA\cup T 的含义为当前钦定限制下,所有必须为 0 的位置集合,因此 (AT)c(A\cup T)^c 就是 ATA\cup T 的补集,其代表所有可以为 1 的位置集合,f(x)f(x) 表示子集和,即所有满足以下条件的数字 yy权值和

  • 若设 aa 的从右到左第 ii 个二进制位为 aia_i,则当 xi=0x_i=0 时,yi=0y_i=0.即 xx 中为 0 的位 yy 中也必须为 0.

公式化的,其统计的是 Txw(T)\sum_{T\subseteq x}w(T)

f(x)f(x) 可以通过高维前缀和预处理出来,在后文会详细说明.

int U = (1 << n) - 1;
// 枚举 T,T 表示我们钦定这些部分应该填 0,即使模式串中为 1
for (int T = B; T; T = (T - 1) & B) {
int NA = A | T;
int Com = U ^ NA;
if (__builtin_popcount(T) & 1) {
ans -= f[Com];
} else {
ans += f[Com];
}
}
int Com = U ^ A;
ans += f[Com];

最后,对于 f(x)f(x)g(x)g(x) 的预处理,可以使用高维前后缀和.

我们知道,对于一维的前缀和,其转移方程是这样的:

for(int i = 1; i <= n; i++)
a[i] += a[i-1];

而对于二维,可以使用容斥,也可以一维一维地求:

for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
a[i][j] += a[i - 1][j];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
a[i][j] += a[i][j - 1];

对于高维前缀和,可以将其视作多维前缀和,这里 n=20n=20,就是 20 维空间,每一维的大小是 2,即只有 0 和 1 两种状态.我们对于每一个维度,都尝试将 0 的统计值加到 1 的头上.高维后缀和正好相反,它尝试将 1 的统计值加到 0 的头上.

可以使用下面的代码,时间复杂度 O(n2n)O(n2^n)

for (int i = 0; i < n; i++) { // 枚举维度
for (int T = 0; T < (1 << n); T++) { // 枚举所有的状态
if (T >> i & 1) { // 如果第 i 位是 0
// 就把状态加到同维 1 的头上
f[T] += f[T ^ (1 << i)];
} else { // 如果第 i 位是 1
// 就把状态加到同维 0 的头上
g[T] += g[T ^ (1 << i)];
}
}
}

整体的复杂度为 O(n2n+q2L3)O(n2^n+\sqrt[3]{q2^L}),这种分类讨论是一种分治技巧,常称之为根号分治

标程

#define IO(x) freopen(x ".in", "r", stdin), freopen(x ".out", "w", stdout)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 100;
int n, q, w[N], f[N], g[N];
int A, B, C, ans;
void solve_A() {
// 0 居少
// 枚举必须为 0 中为 1 的位置
for (int T = A; T > 0; T = (T - 1) & A) {
int U = B | T;
if (__builtin_popcount(T) & 1) {
ans -= g[U];
} else {
ans += g[U];
}
}
ans += g[B];
}
void solve_B() {
// 1 较少
// 枚举必须为 0 的位置
int U = (1 << n) - 1;
for (int T = B; T; T = (T - 1) & B) {
int NA = A | T;
int Com = U ^ NA;
if (__builtin_popcount(T) & 1) {
ans -= f[Com];
} else {
ans += f[Com];
}
}
int Com = U ^ A;
ans += f[Com];
}
void solve_C() {
// 问号居少,暴力
for (int T = C; T > 0; T = (T - 1) & C) {
int U = B | T;
ans += w[U];
}
ans += w[B];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
string str;
cin >> str;
for (int i = 0; i < (1 << n); i++) {
w[i] = str[i] - '0';
f[i] = g[i] = w[i];
}
for (int i = 0; i < n; i++) {
for (int T = 0; T < (1 << n); T++) {
if (T >> i & 1) {
f[T] += f[T ^ (1 << i)];
} else {
g[T] += g[T ^ (1 << i)];
}
}
}
while (q--) {
string str;
cin >> str;
A = 0, B = 0, C = 0, ans = 0;
for (int i = 0; i < str.size(); i++) {
char ch = str[n - i - 1];
if (ch == '0') {
A |= (1 << i);
} else if (ch == '1') {
B |= (1 << i);
} else {
C |= (1 << i);
}
}
if (__builtin_popcount(A) <= n / 3) {
solve_A();
} else if (__builtin_popcount(B) <= n / 3) {
solve_B();
} else {
solve_C();
}
cout << ans << endl;
}
return 0;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D17 T4
https://blog.jerrylab.top/posts/problem/anji2026/D17/T4/
作者
Jerry
发布于
2026-08-19
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0