二进制
二进制
给定一个长度为 $N$ 的二进制串($01$ 串)以及一个正整数 $K$。
按照从左到右的顺序,依次遍历给定二进制串的 $N-K+1$ 个长度为 $K$ 的子串,并计算每个遍历子串的各位数字之和。
将这 $N-K+1$ 个子串数字和按照子串的遍历顺序进行排列,得到的序列就是给定二进制串的 $\texttt{K-}$子串数字和序列。
注意,所有子串数字和均用十进制表示。
例如,当 $K=4$ 时,二进制串 110010 的 $\texttt{K-}$子串数字和序列为 2 2 1,分析过程如下:
- 依次遍历 110010 的所有长度为 $4$ 的子串:1100 、1001 、0010。
- 计算每个遍历子串的各位数字之和:$1 + 1 + 0 + 0 = 2$、$1 + 0 + 0 + 1 = 2$、$0 + 0 + 1 + 0 = 1$。
- 将所有子串数字和按顺序排列,最终得到 2 2 1。
现在,给定 $N,K$ 以及一个 $\texttt{K-}$子串数字和序列,请你计算一共有多少个不同的长度为 $N$ 的二进制串可以得到该 $\texttt{K-}$子串数字和序列。
数据保证:至少存在一个长度为 $N$ 的二进制串可以得到该 $\texttt{K-}$子串数字和序列。
由于结果可能很大,你只需要输出对 $10^6+3$ 取模后的结果。
输入格式
第一行包含两个整数 $N,K$。
第二行包含 $N-K+1$ 个整数,表示给定的 $\texttt{K-}$子串数字和序列。
输入保证给定的 $\texttt{K-}$子串数字和序列一定合法,即至少存在一个满足条件的二进制串与之对应。
不难发现,长度为 $K$ 的子串的各位数字之和一定不小于 $0$ 且不大于 $K$。
输出格式
一个整数,表示满足条件的二进制串数量对 $10^6+3$ 取模后的结果。
数据范围
$1 \le K \le N \le 10^6$
输入样例:
7 4 3 2 2 2
输出样例:
3
样例解释
满足条件的二进制串一共有 $3$ 个,分别为 1011001,1101010,1110011 。
解题思路
找性质,以样例为例,假设长度为$n$的二进制串用序列$p$来表示,对于相邻的两个数$s_0$和$s_1$有$s_0 = p_0 + p_1 + p_2 + p_3$,$s_1 = p_1 + p_2 + p_3 + p_4$。可以发现只有$p_0$和$p_4$不一样,其他$3$位$p_1$、$p_2$、$p_3$都是一样的,又因为$s_0 = 3$比$s_1 = 2$多$1$,因此必然有$p_0 - p_4 = 1$,又因为$p_i \in \{ 0,1 \}$,那么就有$p_0 = 1$,$p_4 = 0$。继续分析相邻的$s_1$和$s_2$,可以发现只有$p_1$和$p_5$不一样,又因为$s_1 = s_2$,因此必然有$p_1 = p_5$。同理可得$p_2 = p_6$。
根据分析的结果来看一下可以得到多少个不同的序列$p$,我们只需看第一个长度为$k$的子串(即$s_0$)未确定的数位有多少种选择。此时$p_0$已经确定是$1$了,剩余三位还未确定,又因为$s_0 = 3$,故需要在$p_1$、$p_2$、$p_3$中选择两个位作为$1$,另一个作为$0$,因此就有$C_{3}^{2} = 3$种选择。可以发现当第一个长度为$k$的子串中的每一位确定后,$p$中剩余位也都确定了,这是因为$p_4 = 0$,$p_5 = p_1$,$p_6 = p_2$。故$p$一共有$3$种不同的序列。
对于一般的情况也是类似的。遍历所有相邻的$s_i$和$s_{i+1}$,那么就会有三种情况:
- $s_i = s_{i+1}$:那么就有$p_i = p_{i+k}$。
- $s_i > s_{i+1}$:那么就有$p_i = 1$,$p_{i+k} = 0$。
- $s_i < s_{i+1}$:那么就有$p_i = 0$,$p_{i+k} = 1$。
其中对于$s_i = s_{i+1}$的情况,此时$p_i$和$p_{i+k}$可能无法确定选$0$还是$1$,但有可能会被后面的数通过第$2$和第$3$种情况确定出来。比如如果枚举到$s_{i+k}$时有$p_{i+k} = 1$,$p_{i+2k} = 0$,那么就能确定出来$p_i = 1$。对于相等的情况我们用并查集来维护某个集合选$0$还是$1$。
在确定$p$中某些位置明确选什么数以及哪些位置选相同的数后,统计第一个长度为$k$的子串中明确选择$0$与$1$的位置数量,分别记作$c_0$和$c_1$,因此还需要在$k - c_0 - c_1$个未确定的位中选出$s_0 - c_1$个$1$,那么答案就是$C_{k - c_0 - c_1}^{s_0 - c_1}$。而第一个子串中的位都确定后,$p$中剩余的位也都确定了,直观来理解的话对于任意剩余的位$p_i \ \left(i \in [k, n-1] \right)$,我们可以不断往前推,即考虑$p_{i-k}, p_{i-2k}, \ldots, p_{i \bmod k}$这些位。由于此时$p_{i \bmod k}$确定了,因此可以发现$p_i$始终可以推到已确定值的某个位置。也可以用数学归纳法来证明。
首先第一个子串已经确定了,假设第$i$个子串确定了,看一下第$i+1$个子串是否确定。根据上面的分析可以确定$p_i$和$p_{i+k}$的取值,由于$p_i$确定了,因此$p_{i+k}$也明确选$0$或$1$,又因为$p_{i+1} \sim p_{i+k-1}$确定了,因此第$i+1$个子串也确定了。同理可以证明如果第$i$个子串的和等于$s_i$,那么第$i+1$个子串的和等于$s_{i+1}$。
AC代码如下,时间复杂度为$O(n + \log{\text{mod}})$:
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