二进制

二进制

给定一个长度为 N 的二进制串(01 串)以及一个正整数 K

按照从左到右的顺序,依次遍历给定二进制串的 NK+1 个长度为 K 的子串,并计算每个遍历子串的各位数字之和。

将这 NK+1 个子串数字和按照子串的遍历顺序进行排列,得到的序列就是给定二进制串的 K-子串数字和序列。

注意,所有子串数字和均用十进制表示。

例如,当 K=4 时,二进制串 110010 K-子串数字和序列为 2 2 1,分析过程如下:

  • 依次遍历 110010 的所有长度为 4 的子串:1100 、1001 、0010
  • 计算每个遍历子串的各位数字之和:1+1+0+0=21+0+0+1=20+0+1+0=1
  • 将所有子串数字和按顺序排列,最终得到 2 2 1

现在,给定 N,K 以及一个 K-子串数字和序列,请你计算一共有多少个不同的长度为 N 的二进制串可以得到该 K-子串数字和序列。

数据保证:至少存在一个长度为 N 的二进制串可以得到该 K-子串数字和序列。

由于结果可能很大,你只需要输出对 106+3 取模后的结果。

输入格式

第一行包含两个整数 N,K

第二行包含 NK+1 个整数,表示给定的 K-子串数字和序列。

输入保证给定的 K-子串数字和序列一定合法,即至少存在一个满足条件的二进制串与之对应。

不难发现,长度为 K 的子串的各位数字之和一定不小于 0 且不大于 K

输出格式

一个整数,表示满足条件的二进制串数量对 106+3 取模后的结果。

数据范围

1KN106

输入样例:

7 4
3 2 2 2

输出样例:

3

样例解释

满足条件的二进制串一共有 3 个,分别为 101100111010101110011 。

 

解题思路

  找性质,以样例为例,假设长度为n的二进制串用序列p来表示,对于相邻的两个数s0s1s0=p0+p1+p2+p3s1=p1+p2+p3+p4。可以发现只有p0p4不一样,其他3p1p2p3都是一样的,又因为s0=3s1=21,因此必然有p0p4=1,又因为pi{0,1},那么就有p0=1p4=0。继续分析相邻的s1s2,可以发现只有p1p5不一样,又因为s1=s2,因此必然有p1=p5。同理可得p2=p6

  根据分析的结果来看一下可以得到多少个不同的序列p,我们只需看第一个长度为k的子串(即s0)未确定的数位有多少种选择。此时p0已经确定是1了,剩余三位还未确定,又因为s0=3,故需要在p1p2p3中选择两个位作为1,另一个作为0,因此就有C32=3种选择。可以发现当第一个长度为k的子串中的每一位确定后,p中剩余位也都确定了,这是因为p4=0p5=p1p6=p2。故p一共有3种不同的序列。

  对于一般的情况也是类似的。遍历所有相邻的sisi+1,那么就会有三种情况:

  1. si=si+1:那么就有pi=pi+k
  2. si>si+1:那么就有pi=1pi+k=0
  3. si<si+1:那么就有pi=0pi+k=1

  其中对于si=si+1的情况,此时pipi+k可能无法确定选0还是1,但有可能会被后面的数通过第2和第3种情况确定出来。比如如果枚举到si+k时有pi+k=1pi+2k=0,那么就能确定出来pi=1。对于相等的情况我们用并查集来维护某个集合选0还是1

  在确定p中某些位置明确选什么数以及哪些位置选相同的数后,统计第一个长度为k的子串中明确选择01的位置数量,分别记作c0c1,因此还需要在kc0c1个未确定的位中选出s0c11,那么答案就是Ckc0c1s0c1。而第一个子串中的位都确定后,p中剩余的位也都确定了,直观来理解的话对于任意剩余的位pi (i[k,n1]),我们可以不断往前推,即考虑pik,pi2k,,pimodk这些位。由于此时pimodk确定了,因此可以发现pi始终可以推到已确定值的某个位置。也可以用数学归纳法来证明。

  首先第一个子串已经确定了,假设第i个子串确定了,看一下第i+1个子串是否确定。根据上面的分析可以确定pipi+k的取值,由于pi确定了,因此pi+k也明确选01,又因为pi+1pi+k1确定了,因此第i+1个子串也确定了。同理可以证明如果第i个子串的和等于si,那么第i+1个子串的和等于si+1

  AC代码如下,时间复杂度为O(n+logmod)

复制代码
 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/

posted @   onlyblues  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-08-26 基环树的定义
Web Analytics
点击右上角即可分享
微信分享提示