跳转至

歪比歪比

题意

求满足以下条件的序列对 (A,B) 的个数:

  • A,B 中只含 -m1
  • A,B-m 的个数的总和为 n
  • A 中所有数的和为 S_AB 中所有数的和为 S_B
  • A,B 中任意位置的前缀和都大于 0

多组询问。

解析

对于一个只含负数和 1,且所有数总和为 S (S>0) 的 不循环 序列 A,其有恰好 S 个循环同构串的所有前缀和都大于 0

证明

考虑将 A 循环地写出,即 B=[A_1,A_2,\ldots,A_{|A|},A_1,A_2,\ldots,A_{|A|},A_1,\ldots]。这个循环数列的前缀和可以表示为 s_1,s_2,\ldots,s_1+S,s_2+S,\ldots

对于 i\in[0,n)Bi+1 开始的长度为 |A| 的子串的所有前缀和都大于 0,当且仅当 \forall j>i,s_j> s_i。这一点很容易理解,因为 S>0,则 \forall j\in[i+1,i+n],s_j>s_i\iff\forall j>i,s_j>s_i

\exists j>i,s_j\le s_i,则一定有 \exists j>i,s_j=s_i,因为 s 总体是增加的,而增加量总是为 1。于是,\forall j>i,s_j>s_i\forall j>i,s_j\neq s_i 是等价的 (因为其中任意一个不成立时,另一个也不成立)。

于是 A 的所有前缀和都大于 0 的循环同构串的个数就是 \sum\limits_{i=0}^{|A|-1}[\forall j>i,s_j\neq s_i]。对于 [0,|A|-1] 范围的所有 s_iS 同余的 i,它们中有且仅有一个能计入答案。因为对于每个 s_is_i+S,s_i+2S,s_i+3S,\ldots 总是会出现在前缀和的某个位置。而又由于 s 的增量至多为 1,于是所有 s_i\bmod S 正好构成 S 的完全剩余系,正好有 S 个计入答案。

我们要求出含 n-m,总和为 S 且所有前缀和都大于 0 的序列 A 的个数。在所有含 n-m 且总和为 S 的序列中,每个不循环序列会贡献 \frac{S}{|A|} 至答案,每个循环 x 的序列会贡献 \frac{\frac{S}{x}}{\frac{|A|}{x}}=\frac{S}{|A|} 至答案,故答案为 \frac{S}{|A|}\binom{|A|}{n}

如果枚举 n-m 中被分配到 A 的个数,我们就可以 O(n) 得到单组询问的答案了。不过我们还发现,每个含 n-m,总和为 S_A+S_B 的合法序列都存在唯一的一个位置 i 满足 s_i=S_A,\forall j>i,s_j\neq S_A,从那里断开可以得到合法的 AB

实现

#include <bits/stdc++.h>

const int maxn = 3e7 + 19, mod = 998244353;

int qpow(int a, int b){
    int res = 1;
    while(b){
        if(b & 1) res = (long long int)res * a % mod;
        a = (long long int)a * a % mod, b >>= 1;
    }
    return res;
}

int fact[maxn], ifact[maxn];

void init(int n){
    fact[0] = 1;
    for(int i = 1; i <= n; ++i)
        fact[i] = (long long int)fact[i - 1] * i % mod;
    ifact[n] = qpow(fact[n], mod - 2);
    for(int i = n - 1; i >= 0; --i)
        ifact[i] = (long long int)ifact[i + 1] * (i + 1) % mod;
}

inline int inv(int n){
    return (long long int)ifact[n] * fact[n - 1] % mod;
}

inline int binom(int n, int m){
    return (long long int)fact[n] * ifact[n - m] % mod * ifact[m] % mod;
}

int main(){
    init(3e7);
    int T; std::scanf("%d", &T);
    while(T--){
        static int n, m, Sa, Sb;
        std::scanf("%d%d%d%d", &n, &m, &Sa, &Sb);
        int len = n * (m + 1) + Sa + Sb,
            ans = (long long int)binom(len, n) * (Sa + Sb) % mod * inv(len) % mod;
        std::printf("%d\n", ans);
    }
}

来源

NOI.AC #2034 歪比歪比

评论