【纪中集训2019.3.25】染色问题

题目

描述

​ 有一个纸片,纸片上有\(n\)个格子,初始时没有颜色;

​ 某个游戏的内容是进行\(m\)次染色,使得染完后\(n\)个格子一定有颜色;

​ 每次可以选择一个区间\([l,r](l \le r)\)去染(不能不染),颜色可以覆盖;

​ 问最后染出的序列有多少种;

范围

\(n,m \le 10^6\) ;

题解

  • 由于可以覆盖这个条件处理起来比较麻烦;

  • 考虑每次是插入一段颜色;

  • \[ \begin{cases} &f_{i,j} = f_{i-1,j} + \sum_{k=0}^{j-1} \ f_{i-1,k}(k+1) &(i \lt m)\\ &f_{i,j} = \sum_{k=1}^{j-1} \ f_{i-1,k}(k+1) &(i = m) \\ \end{cases} \]

    • 插播一个我自己的十分没用的想法:

    • 只考虑管第一个转移,设 $ F_{i} $ 为 $ f_{i,j} $ 的 $ OGF $ 对比 $ f_{i,j}和f_{i,j-1} $ ,有:

    • \[ F_{i} = F_{i-1} + xF_i + x^2(F_{i-1})' ; \\ F_{i} = \frac{(x^2F_{i-1})'-F_{i-1}}{1-x} ;\\ \]

    • 然后就没有然后了,。。。。。,TAT​;

  • 说正解:

  • 方程中存在两类贡献,一种是\(f_{i,j} \times (j+1)\),一种是\(f_{i,j}\times 1\);

  • 同时要求最后一次转移一定是第二种;

  • 枚举第一种转移的次数\(k\),求得贡献和后乘以\((^{m-1}_{k-1})\)

  • 考虑每次更新之后的形成的第一种转移次数的序列:\(0 \lt a_1 \lt a_2 \ \lt ,\cdots, \lt a_k = n\)

  • 对于一种转移序列的贡献就是:\((a_1+1)\cdots(a_{k-1}+1)\) ;

  • 这其实是\([x^{n-k}]\Pi_{i=1}^{n-1}(x+i+1)\) ;

  • 所以答案是:\(\sum_{k=1}^{m} (^{m-1}_{k-1})[x^{n-k}]\Pi_{i=1}^{n-1}(x+i+1)\);

  • 类似https://www.cnblogs.com/Paul-Guderian/p/10519990.html倍增可以\(nlogn\) ;

    #include<bits/stdc++.h>
    #define ll long long 
    #define mod 998244353
    #define il inline 
    using namespace std;
    const int N=4000010;
    int n,m,fac[N],inv[N],len,L,rev[N],f[N],g[N],a[N],b[N];
    il int pw(int x,int y){
      int re=1;
      if(y<0)y+=mod-1; 
      while(y){
          if(y&1)re=(ll)re*x%mod;
          y>>=1;x=(ll)x*x%mod;
      }
      return  re;
    }
    il void inc(int&x,int y){x+=y;if(x>=mod)x-=mod;}
    const int G=3;
    il 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)/(i<<1));
          for(int j=0;j<len;j+=i<<1){
              int w=1;
              for(int k=0;k<i;++k,w=(ll)w*wn%mod){
                  int x=A[j+k],y=(ll)w*A[j+k+i]%mod;
                  A[j+k]=(x+y)%mod,A[j+k+i]=(x-y+mod)%mod;
              }
          }
      }
      if(!~F){
          int iv=pw(len,mod-2);
          for(int i=0;i<len;++i)A[i]=(ll)A[i]*iv%mod;
      }
    }
    void solve(int n){
      if(n==1){f[0]=2;f[1]=1;return;}
      if(n&1){
          solve(n-1);
          for(int i=n-1;~i;--i)inc(f[i+1],f[i]),f[i]=(ll)(n+1)*f[i]%mod;
          return ;
      }
      solve(n>>=1);
      for(len=1,L=0;len<(n+1)<<1;L++,len<<=1);
      for(int i=0;i<=len;++i){
          rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
          a[i]=b[i]=g[i]=0;
      }
      for(int i=0,pwn=1;i<=n;++i,pwn=(ll)pwn*n%mod){
          a[i]=(ll)f[n-i]*fac[n-i]%mod;
          b[i]=(ll)pwn*inv[i]%mod;
      }
      ntt(a,1);ntt(b,1);
      for(int i=0;i<len;++i)a[i]=(ll)a[i]*b[i]%mod;
      ntt(a,-1);
      for(int i=0;i<=n;++i)g[i]=(ll)inv[i]*a[n-i]%mod;
      ntt(f,1);ntt(g,1);
      for(int i=0;i<len;++i)f[i]=(ll)f[i]*g[i]%mod;
      ntt(f,-1);
    }
    int C(int x,int y){return (ll)fac[x]*inv[y]%mod*inv[x-y]%mod;}
    int main(){
      freopen("color.in","r",stdin);
      freopen("color.out","w",stdout);
      scanf("%d%d",&n,&m);
      for(int i=fac[0]=inv[0]=1;i<=m||i<=n;++i){
          fac[i]=(ll)fac[i-1]*i%mod;
          inv[i]=pw(fac[i],mod-2);
      }
      solve(n-1);
      int ans=0;
      for(int i=1;i<=n&&i<=m;++i){
          int tmp=(ll)f[n-i]*C(m-1,i-1)%mod;
          inc(ans,tmp);
      }
      cout<<ans<<endl;
      return 0;
    }

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值