安吉D17 T4
- 1安吉D19 T1
- 2安吉D19 T3
- 3安吉D20 模考12总结
- 4安吉D17 T3
- 5安吉D17 T4本文
- 6安吉D19 模考11总结
- 7安吉D15 A
- 8安吉D16 T4
- 9安吉D17 T1
- 10安吉D17 模考10总结
- 11安吉D16 T3
- 12安吉D16 模考9总结
- 13安吉D13 模考8总结
- 14安吉D13-T2
- 15安吉D13-T3
- 16安吉D12-T3
- 17安吉D11-B
- 18安吉D11-C
- 19安吉D11-D
- 20安吉D12 模考7总结
- 21安吉D12-T2
- 22安吉D11-A
- 23安吉D10 模考6总结
- 24安吉D10-T3
- 25安吉D10-T4
- 26安吉D8-E
- 27安吉D9-T4
- 28安吉D8-A
- 29安吉D8-B
- 30安吉D8-C
- 31安吉D8-D
- 32安吉D9 模考5总结
- 33安吉D9-T2
- 34安吉D9-T3
- 35安吉D6-T4
- 36安吉D4-G
- 37安吉D5-T2
- 38安吉D6 模考4总结
- 39安吉D6-T1
- 40安吉D6-T3
- 41安吉-开营测试 T4
- 42安吉D5 模考3总结
- 43安吉D5-T3
- 44安吉D4-A
- 45安吉D4-B
- 46安吉D4-C
- 47安吉D4:容斥原理
- 48安吉Day4-D
- 49安吉D3 模考2总结
- 50安吉D3-T1
- 51安吉D3-T2
- 52安吉D3-T4
- 53安吉D2 模考1总结
- 54安吉D2-T2
- 55安吉D1-G
- 56安吉D1-L
- 57安吉D2-T4
原题呈现
P7670 毒蛇越狱 / Snake Escaping
题目描述
JOI 实验室有 条毒蛇.蛇的编号为 .每条蛇从头到尾分为 个部分.每个部分的颜色是蓝色或红色. 对于毒蛇 ,令 ()为 的二进制表达式.那么,
- 如果 ,毒蛇 从头开始的第 部分的颜色是蓝色,
- 如果 ,毒蛇 从头开始的第 部分的颜色是红色.
每条毒蛇都有一个 到 之间的整数,包括 和 ,为毒性.给出一个由 组成的长度为 的字符串 .第 个字符()是毒蛇 的毒性.由于毒蛇行动迅速,所以经常从 JOI 实验室逃走.住在实验室附近的人向 JOI 实验室投诉,他们看到毒蛇从实验室逃逸.您将收到 天的投诉清单. 第 天的投诉()是一个长度为 的字符串 ,由 组成.
- 如果 的第 个字符()为 ,这意味着第 天从实验室逃出的每条毒蛇的第 个部分是蓝色的,
- 如果 的第 个字符()为 ,这意味着第 天从实验室逃出的每条毒蛇的第 部分是红色的,并且
- 如果 的第 个字符()为 ,这意味着人们没有提供关于第 天从实验室逃逸的毒蛇的第 部分的信息.
所有的投诉都是准确的信息.所有从实验室逃逸的毒蛇都在同一天被 JOI 实验室的工作人员收留.可能发生同一条蛇在不同的日子逃脱. JOI 实验室执行主任 K 教授为了估计毒蛇逃逸的风险,想知道可能逃出实验室的毒蛇的毒性总和. 你的任务是编写一个程序,根据 天的投诉列表,计算每天可能从实验室逃逸的蛇的毒性总和. 现给定描述毒蛇毒性的字符串 和 天的投诉列表,请编写一个程序来计算每天可能从实验室逃逸的蛇的毒性总和.
输入格式
第一行包含两个空格分隔的整数 ,,分别是每条毒蛇的部位数和投诉天数.第二行包含长度为 的字符串 ,描述了毒蛇的毒性.后面 行的第 行包含一个长度为 的字符串 ,为第 天的投诉.
输出格式
共 行,第 行应包含一个整数,即第 天可能从实验室逃逸的蛇的毒性总和.
输入输出样例 #1
输入 #1
3 5123456780000??1?0?11???输出 #1
110121236输入输出样例 #2
输入 #2
4 831415926535897930101?01???1??0??1?0001?1??10????输出 #2
918383014152080说明/提示
数据规模与约定
对于 的数据,,, 是长度为 的字符串,字符串 由 组成, 是长度为 ()的字符串,字符串 由 ()组成.
- Subtask ( points):,.
- Subtask ( points):.
- Subtask ( points):.
- Subtask ( points):.
- Subtask ( points):没有额外的限制.
样例说明
对于样例 :,共 条毒蛇,它们中的每一条都分为 个部分. 投诉时间为 天.
- 第一天,可能逃出实验室的毒蛇只有毒蛇 .毒性总和为 .
- 第二天,可能从实验室逃逸的毒蛇是毒蛇 .毒性总和为 .
- 第三天,可能从实验室逃逸的毒蛇是毒蛇 .毒性总和为 .
- 第四天,可能从实验室逃逸的毒蛇是毒蛇 .毒性总和是 .
- 第五天,可能从实验室逃逸的毒蛇是毒蛇 .毒性总和为 .
此题题意就是给定一个模式串,求二进制表示匹配这个模式串的所有数权值之和.
最容易想到的就是枚举所有问号部分应该填什么,然后将所有可能的数字权值加起来.下面的程序实现了这个暴力.其中,
A的二进制上每一位是 1,当且仅当模式串上这一位是0.B的二进制上每一位是 1,当且仅当模式串上这一位是1.C的二进制上每一位是 1,当且仅当模式串上这一位是?.
// 枚举 T,T 表示我们钦定这些部分应该填 1for (int T = C; T > 0; T = (T - 1) & C) { int U = B | T; ans += w[U];}ans += w[B];程序的复杂度高达 ,需要优化.
在复杂度中, 在指数位上,只需要让 就可以了.因此,对于问号数量小于等于 8 的模式串,可以使用上面的暴力.那对于问号数量大于 8 的呢?问号数量既然大于 8,由于 ,那么必然会有 1 的数量小于 8,或者 0 的数量小于 8.对于这两种情况分别考虑.
对于 0 的数量小于 8 的情况,我们可以尝试使用容斥.具体来说,对于每一个为 0 的位置,设其组成一个集合,即为上述的 ,则满足条件的情况为:模式串中每一个规定为 0 的位置都全部遵守的权值,减去钦定有一个位置不遵守的权值,加上有钦定两个位置不遵守的权值……公式化的,设钦定模式串中为 0 的位置必须为 1 的位置集合是 ,则有:
其中, 的含义为上文所述,故 的含义为当前钦定限制下,所有必须为 1 的位置集合, 表示超集和,即所有满足以下条件的数字 的权值和:
- 若设 的从右到左第 个二进制位为 ,则当 时,.即 中为 1 的位 中也必须为 1.
公式化的,其统计的是 .
可以通过高维后缀和预处理出来,在后文会详细说明.
// 枚举 T,T 表示我们钦定这些部分应该填 1,即使模式串中为 0for (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 的位置,设其组成一个集合,即为上述的 ,则满足条件的情况为:模式串中每一个规定为 1 的位置都全部遵守的权值,减去钦定有一个位置不遵守的权值,加上有钦定两个位置不遵守的权值……公式化的,设钦定模式串中为 1 的位置必须为 0 的位置集合是 ,则有:
其中, 的含义为当前钦定限制下,所有必须为 0 的位置集合,因此 就是 的补集,其代表所有可以为 1 的位置集合, 表示子集和,即所有满足以下条件的数字 的权值和:
- 若设 的从右到左第 个二进制位为 ,则当 时,.即 中为 0 的位 中也必须为 0.
公式化的,其统计的是 .
可以通过高维前缀和预处理出来,在后文会详细说明.
int U = (1 << n) - 1;// 枚举 T,T 表示我们钦定这些部分应该填 0,即使模式串中为 1for (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];最后,对于 和 的预处理,可以使用高维前后缀和.
我们知道,对于一维的前缀和,其转移方程是这样的:
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];对于高维前缀和,可以将其视作多维前缀和,这里 ,就是 20 维空间,每一维的大小是 2,即只有 0 和 1 两种状态.我们对于每一个维度,都尝试将 0 的统计值加到 1 的头上.高维后缀和正好相反,它尝试将 1 的统计值加到 0 的头上.
可以使用下面的代码,时间复杂度 .
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)]; } }}整体的复杂度为 ,这种分类讨论是一种分治技巧,常称之为根号分治.
标程
#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;}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


