二进制
二进制
给定一个长度为 的二进制串( 串)以及一个正整数 。
按照从左到右的顺序,依次遍历给定二进制串的 个长度为 的子串,并计算每个遍历子串的各位数字之和。
将这 个子串数字和按照子串的遍历顺序进行排列,得到的序列就是给定二进制串的 子串数字和序列。
注意,所有子串数字和均用十进制表示。
例如,当 时,二进制串 110010 的 子串数字和序列为 2 2 1,分析过程如下:
- 依次遍历 110010 的所有长度为 的子串:1100 、1001 、0010。
- 计算每个遍历子串的各位数字之和:、、。
- 将所有子串数字和按顺序排列,最终得到 2 2 1。
现在,给定 以及一个 子串数字和序列,请你计算一共有多少个不同的长度为 的二进制串可以得到该 子串数字和序列。
数据保证:至少存在一个长度为 的二进制串可以得到该 子串数字和序列。
由于结果可能很大,你只需要输出对 取模后的结果。
输入格式
第一行包含两个整数 。
第二行包含 个整数,表示给定的 子串数字和序列。
输入保证给定的 子串数字和序列一定合法,即至少存在一个满足条件的二进制串与之对应。
不难发现,长度为 的子串的各位数字之和一定不小于 且不大于 。
输出格式
一个整数,表示满足条件的二进制串数量对 取模后的结果。
数据范围
输入样例:
7 4 3 2 2 2
输出样例:
3
样例解释
满足条件的二进制串一共有 个,分别为 1011001,1101010,1110011 。
解题思路
找性质,以样例为例,假设长度为的二进制串用序列来表示,对于相邻的两个数和有,。可以发现只有和不一样,其他位、、都是一样的,又因为比多,因此必然有,又因为,那么就有,。继续分析相邻的和,可以发现只有和不一样,又因为,因此必然有。同理可得。
根据分析的结果来看一下可以得到多少个不同的序列,我们只需看第一个长度为的子串(即)未确定的数位有多少种选择。此时已经确定是了,剩余三位还未确定,又因为,故需要在、、中选择两个位作为,另一个作为,因此就有种选择。可以发现当第一个长度为的子串中的每一位确定后,中剩余位也都确定了,这是因为,,。故一共有种不同的序列。
对于一般的情况也是类似的。遍历所有相邻的和,那么就会有三种情况:
- :那么就有。
- :那么就有,。
- :那么就有,。
其中对于的情况,此时和可能无法确定选还是,但有可能会被后面的数通过第和第种情况确定出来。比如如果枚举到时有,,那么就能确定出来。对于相等的情况我们用并查集来维护某个集合选还是。
在确定中某些位置明确选什么数以及哪些位置选相同的数后,统计第一个长度为的子串中明确选择与的位置数量,分别记作和,因此还需要在个未确定的位中选出个,那么答案就是。而第一个子串中的位都确定后,中剩余的位也都确定了,直观来理解的话对于任意剩余的位,我们可以不断往前推,即考虑这些位。由于此时确定了,因此可以发现始终可以推到已确定值的某个位置。也可以用数学归纳法来证明。
首先第一个子串已经确定了,假设第个子串确定了,看一下第个子串是否确定。根据上面的分析可以确定和的取值,由于确定了,因此也明确选或,又因为确定了,因此第个子串也确定了。同理可以证明如果第个子串的和等于,那么第个子串的和等于。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e6 + 10, mod = 1e6 + 3; 5 6 int a[N]; 7 int fa[N], v[N]; 8 9 int find(int x) { 10 return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); 11 } 12 13 int qmi(int a, int k) { 14 int ret = 1; 15 while (k) { 16 if (k & 1) ret = 1ll * ret * a % mod; 17 a = 1ll * a * a % mod; 18 k >>= 1; 19 } 20 return ret; 21 } 22 23 int C(int a, int b) { 24 int up = 1, down = 1; 25 for (int i = 1, j = a; i <= b; i++, j--) { 26 up = 1ll * up * j % mod; 27 down = 1ll * down * i % mod; 28 } 29 return 1ll * up * qmi(down, mod - 2) % mod; 30 } 31 32 int main() { 33 int n, m; 34 scanf("%d %d", &n, &m); 35 for (int i = 0; i < n - m + 1; i++) { 36 scanf("%d", a + i); 37 } 38 for (int i = 0; i < n; i++) { 39 fa[i] = i; 40 v[i] = -1; // -1表示不确定选0还是1 41 } 42 for (int i = 0; i < n - m; i++) { 43 int x = find(i), y = find(i + m); 44 if (a[i] == a[i + 1]) v[y] = v[x], fa[x] = y; // 这里要把较大的位y并到较小的位x,因为较小的位的v[x]可能确定了选什么,而v[y]=-1,因此应该把y并到x 45 else if (a[i] > a[i + 1]) v[x] = 1, v[y] = 0; 46 else v[x] = 0, v[y] = 1; 47 } 48 int s0 = 0, s1 = 0; 49 for (int i = 0; i < m; i++) { 50 if (v[find(i)] == 0) s0++; 51 else if (v[find(i)] == 1) s1++; 52 } 53 printf("%d", C(m - s0 - s1, a[0] - s1)); 54 55 return 0; 56 }
参考资料
AcWing 5170. 二进制(秋季每日一题2023):https://www.acwing.com/video/4880/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17658377.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-08-26 基环树的定义