【JZOJ6225】【20190618】计数

题目

对于一个01串,定义\(f(s)\)\(f(s) = \sum_{i=0}^{\lfloor \frac{|s|}{2} \rfloor -1 }[s_i=s_{|s|-1-i}]\)

定义\(S\)所有子串集合为\(P(S)\) ,求\(\sum_{s \in P(S)} f(s)\)

\(|S| \le 250000\)

题解

  • 这题我看看到数据范围感到莫名奇怪却不知道奇怪在哪里?

  • 60 pts

  • \(f_{i}\)表示\(i\)位置开头的子序列的贡献,考虑增量

  • 每次增量一个$ s_n $即对所有的 $ i \lt n $ 执行 $ f_i = \sum_{j=i}^{n-1} f_j + 2^{n-i-1}[s_i==s_j] $

  • 相当于把所有位置做后缀和再在和$ s_n $相同的位置加上一个什么东西。。。

  • 后缀和好像没法优化,那似乎如果我们直接出一个维护每次后缀和再考虑这种奇怪的加一个东西,

    就可以转化成下面的100分做法来做了,可能装饰一下会是个不错的题?

  • 100 pts

  • \[ \begin{align} ans &= \sum_{i=0}^{n-1}\sum_{j\gt i}^{n-1} [s_i=s_j]2^{j-i-1}\sum_{k=0}^{min(i-1,n-1-j)}(^{i-1}_{k})(^{n-1-j}_k)\\ 由于&\sum_{k=0} (^i_k)(^j_k) = \sum_{k=0} (^i_k)(^j_{j-k}) = (^{i+j}_j) \\ ans &= \sum_{i=0}^{n-1}\sum_{j\gt i}^{n-1} [s_i=s_j]2^{j-i-1}(^{n+i-j-2}_{i-1}) \\ &考虑01串并记reverse(s)=t,组合数拆开m,做两遍下面的东西\\ &= \sum_{k=0}^{n-2} 2^k(n-2-k)! \sum_{i=0}^{n-2-k} \frac{s_i}{i!} \times \frac{t_{n-2-k-i}}{(n-2-k-i)!} \\ &直接卷就可以了\\ \end{align} \]

    • 之后我知道哪里奇怪了,FFT的话大致要四倍的数组,而2.5e5*4=1e6,十分惊人的暗示!
    #include<bits/stdc++.h>
    #define mod 998244353
    
    using namespace std;
    
    const int N=1000010,G=3;
    int n,a[N],b[N],c[N],ny[N],fac[N],inv[N],L,len,rev[N],pw2[N],iv;
    char s[N];
    
    void inc(int&x,int y){x+=y;if(x>=mod)x-=mod;}
    int pw(int x,int y){
      int re=1;if(y<0)y+=mod-1;
      while(y){
          if(y&1)re=1ll*re*x%mod;
          y>>=1;x=1ll*x*x%mod;
      }return re;
    }
    
    void ntt(int*A,int f){
      for(int i=0;i<len;++i)if(i<rev[i])swap(A[i],A[rev[i]]);
      for(int i=1;i<len;i<<=1){
          int wn=pw(G,f*(mod-1)/2/i);
          for(int j=0;j<len;j+=(i<<1)){
              int w=1;
              for(int k=0;k<i;++k,w=1ll*w*wn%mod){
                  int x=A[j+k],y=1ll*w*A[j+k+i]%mod;
                  A[j+k]=(x+y)%mod;A[j+k+i]=(x-y+mod)%mod;    
              }
          }
      }
      if(!~f)for(int i=0;i<len;++i)A[i]=1ll*iv*A[i]%mod;
    }
    
    void calc(){
      ntt(a,1);ntt(b,1);
      for(int i=0;i<len;++i)a[i]=1ll*a[i]*b[i]%mod;
      ntt(a,-1);
      for(int i=0;i<len;++i){inc(c[i],a[i]);a[i]=b[i]=0;}
    }
    
    int main(){
      freopen("count.in","r",stdin);
      freopen("count.out","w",stdout);
      scanf("%s",s);n=strlen(s);
      ny[1]=pw2[0]=1;
      for(int i=pw2[1]=2;i<=n;++i){
          ny[i]=1ll*(mod-mod/i)*ny[mod%i]%mod;
          pw2[i]=(pw2[i-1]<<1)%mod;
      }
      for(int i=fac[0]=inv[0]=1;i<=n;++i){
          fac[i]=1ll*fac[i-1]*i%mod;
          inv[i]=1ll*inv[i-1]*ny[i]%mod;
      }
      for(int i=0;i<n;++i){
          a[i]=s[i]=='1'?inv[i]:0;
          b[i]=s[n-1-i]=='1'?inv[i]:0;    
      }
      for(len=1;len<(n<<1);len<<=1,++L);
      for(int i=0;i<len;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
      iv=pw(len,mod-2);
      calc();
      for(int i=0;i<n;++i){
          a[i]=s[i]=='0'?inv[i]:0;
          b[i]=s[n-1-i]=='0'?inv[i]:0;
      }
      calc();
      int ans=0;
      for(int i=0;i<=n-2;++i)inc(ans,1ll*pw2[i]*fac[n-2-i]%mod*c[n-2-i]%mod);
      cout<<ans<<endl;
      return 0;
    }
    

转载于:https://www.cnblogs.com/Paul-Guderian/p/11051195.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值