【学习笔记】CF1264D2 Beautiful Bracket Sequence (hard version)

考虑固定一种计算贡献的方式,从而构造组合意义。

问题接踵而至。如何计算 ( i , j ) (i,j) (i,j)在所有方案中产生贡献的次数?从内往外考虑,那么要求 [ l , i ] [l,i] [l,i]中的左括号和 [ j , r ] [j,r] [j,r]中的右括号数目相等,不难发现这个限制是充要的。又因为问的就是整个序列的权值,所以设 [ 1 , i ] [1,i] [1,i]中有 a a a个问号, [ j , n ] [j,n] [j,n]中有 b b b个问号,左右括号相差的数目为 k k k,那么方案数 ∑ i ≤ a ( a i ) ( b i − k ) = ∑ i ≤ a ( a a − i ) ( b i − k ) = ( a + b a − k ) \sum_{i\le a}\binom{a}{i}\binom{b}{i-k}=\sum_{i\le a}\binom{a}{a-i}\binom{b}{i-k}=\binom{a+b}{a-k} ia(ia)(ikb)=ia(aia)(ikb)=(aka+b),显然如果对组合数比较熟悉的话是不难推出这个式子的。注意, [ i , j ] [i,j] [i,j]这段序列长什么样子事实上并不影响答案。

怎么优化这个枚举过程呢?显然是固定左端点,唯一需要注意的细节是当左右两边的问号都被确定时,此时右端点的选取是唯一的,也就是说恰好对应一种方案,那么将等号改成不等号就有: ∑ i ≤ a ∑ j ≥ i − k ( b j ) ( a i ) \sum_{i\le a}\sum_{j\ge i-k}\binom{b}{j}\binom{a}{i} iajik(jb)(ia),我们希望 j j j 0 0 0开始枚举,因此不妨变一下形式: ∑ i ≤ a ∑ j ≥ 0 ( b i − k + j ) ( a a − i ) \sum_{i\le a}\sum_{j\ge 0}\binom{b}{i-k+j}\binom{a}{a-i} iaj0(ik+jb)(aia),交换求和顺序就有: ∑ j ≥ 0 ∑ i ≤ a ( b i − k + j ) ( a a − i ) = ∑ j ≥ 0 ( a + b a − k + j ) \sum_{j\ge 0}\sum_{i\le a}\binom{b}{i-k+j}\binom{a}{a-i}=\sum_{j\ge 0}\binom{a+b}{a-k+j} j0ia(ik+jb)(aia)=j0(ak+ja+b),注意到 a + b a+b a+b是定值,于是就做完了。

复杂度 O ( n ) O(n) O(n)

感觉组合数的运算还是比多项式要轻量级的。

#include
#define ll long long
#define fi first
#define se second
#define pb push_back
#define db double
using namespace std;
const int mod=998244353;
const int N=1e6+5;
int n,m,K,sum1,sum2,sum3,a,b;
ll fac[N],inv[N],f[N],f2[N],res;
string s;
ll fpow(ll x,ll y=mod-2){
    ll z(1);
    for(y;y>>=1;){
        if(y&1)z=z*x%mod;
        x=x*x%mod;
    }
    return z;
}
void add(ll &x,ll y){x=(x+y)%mod;}
ll binom(int x,int y){
    if(x<0||y<0||x<y)return 0;
    return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
void init(int n,int m){
    fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
    inv[n]=fpow(fac[n]);for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod;
    for(int i=m;i>=0;i--)f[i]=f[i+1],add(f[i],binom(m,i));
    for(int i=m-1;i>=0;i--)f2[i]=f2[i+1],add(f2[i],binom(m-1,i));
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>s,n=s.size();
    for(int i=0;i<n;i++)if(s[i]=='?')m++;
    init(n,m);
    for(int i=0;i<n;i++)sum1+=(s[i]==')');
    for(int i=0;i<n-1;i++){
        sum2+=(s[i]=='('),sum1-=(s[i]==')'),sum3+=(s[i]=='?');
        if(s[i]=='('){
            K=sum1-sum2;
            a=sum3,b=m-sum3;
            assert(a>=0),assert(b>=0);
            assert(a+b==m);
            add(res,f[max(0,a-K)]);
        }
        else if(s[i]=='?'){
            K=sum1-sum2-1;
            a=sum3-1,b=m-sum3;
            assert(a>=0),assert(b>=0);
            assert(a+b==m-1);
            add(res,f2[max(0,a-K)]);
        }
    }
    cout<<res;
}

你可能感兴趣的:(学习,笔记,c语言)