[计数] [组合数学] P10310 「Cfz Round 2」01 String
posted on 2024-06-18 06:19:48 | under | source
拆贡献,考虑 \(f(l,r)\) 会贡献几次,注意到 \(f_k(1,n)\to f(l,r)\) 其实可以反过来视作 \([l,r]\) 区间逐渐扩大 \(k\) 次,最终变成 \([1,n]\) 的过程。
于是我们计算这个东东就好了,左右端点互相独立,以计算左端点 \(l\) 的拓展为例:记 \(p_i\) 表示 \(i\) 会被选为左端点几次,那么等价于计算 \(0\le p_i\) 且 \(\sum\limits_{i=1}^l p_i=k-1\) 的方案数。这里减一是因为必然有一个左端点为 \(1\)。
简单组合数学得知其方案数为 \({{k-1+l-1}\choose {l-1}}\),右端点同理。
考虑计算 \(f(l,r)\),每个合法子串只关注其开头和结尾即可,形式为 \(0\dots 10\dots 10\dots 1\) 这样子。注意开头结尾是固定的,而中间为相邻的 \(10\),对它们计数即可。
记 \(a_i=\sum\limits_{j=1}^{i} [c_i=0\wedge c_{i+1}=1]\),那么 \(f(l,r)=2^{c_{r-2}-c_l}\)。
不妨令 \(k\) 先减去 \(1\),那么:
但是 \(k\) 很大,大到 \(\rm lucas\) 定理都解决不了。注意到后面的组合数具有某种规律,可以递推计算。具体来说,令 \(C_i={{k+i}\choose i}\),那么有 \(C_i=\frac{(k+i)C_{i-1}}i\)。
(下降幂是啥?蒟蒻不会。)
于是暴力枚举,得到了 \(O(n^2)\) 做法~
进一步优化:
然后后缀和预处理后面的和式,就能 \(O(n)\) 算出了。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5, mod = 1e9 + 7;
int T, n, k, inv[N], a[N], ans, C[N];
int s[N];
char c[N];
inline int qstp(int a, int k) {int res = 1; for(; k > 0; a = a * a % mod, k >>= 1) if(k & 1) res = res * a % mod; return res;}
signed main(){
for(int i = 1; i < N; ++i) inv[i] = qstp(i, mod - 2);
cin >> T;
while(T--){
ans = 0;
scanf("%lld%lld %s", &n, &k, c + 1), --k;
C[0] = 1;
for(int i = 1; i <= n; ++i) C[i] = C[i - 1] * ((i + k) % mod) % mod * inv[i] % mod;
for(int i = 1; i < n; ++i) a[i] = a[i - 1] + (c[i] == '1' && c[i + 1] == '0');
s[n + 1] = 0;
for(int i = n; i >= 2; --i){
s[i] = s[i + 1];
if(c[i] == '1') s[i] = (s[i] + qstp(2, a[i - 2]) * C[n - i] % mod) % mod;
}
for(int i = 1; i < n; ++i){
if(c[i] == '0'){
//貌似有点边界问题,所以把j=i+1单独拿出来算
if(c[i + 1] == '1') ans = (ans + C[i - 1] * C[n - i - 1] % mod) % mod;
ans = (ans + qstp(inv[2], a[i]) * C[i - 1] % mod * (s[i + 2] + mod) % mod) % mod;
}
}
printf("%lld\n", ans);
}
return 0;
}

浙公网安备 33010602011771号