【题解】P4091 [HEOI2016/TJOI2016]求和

【题解】P4091 [HEOI2016/TJOI2016]求和

[P4091 HEOI2016/TJOI2016]求和

可以知道\(i,j\)\(0\)开始是可以的,因为这个时候等于\(0\)。这种题目都要从\(0\)开始或许比较好(Itst语)

然后就开始化式子吧

原式=
\[ \sum_{i=0}^{n} \sum_{j=0}^n {i \brace j}2^j j! \]
斯特林容斥式子展开一下,并且我们知道当\(k>j\)时,\({j \choose k}=0\),所以扩大枚举范围到\(n\)
\[ \sum_{i=0}^n\sum_{j=0}^n j!\sum_{k=0}^n \dfrac 1 {j!}(-1)^k{j \choose k}(j-k)^i \]
只有一项和\(i\)有关,约掉一些东西
\[ \sum_{j=0}^n\sum_{k=0}^j (-1)^k{j \choose k}\sum_{i=0}^n(j-k)^i \]
等比求和(边界情况到时候再考虑)
\[ \sum_{j=0}^n \sum_{k=0}^j (-1)^k{j \choose k}\dfrac {1-(j-k)^{n+1}} {1-(j-k)} \]
拆掉组合数
\[ \sum_{j=0}^n j! \sum_{k=0}^j \dfrac{(-1)^k} {k!}\dfrac {1-(j-k)^{n+1}} {1-(j-k)} \]
就是一个\(NTT\)的式子,\(NTT\)处理就好了。

这题关键就是想到那个...算了没什么关键的,无非就是记得几个公式。

其实关键的就是记得把枚举下标最好扩展到一起,来消除变量之间的相互联系,并且方便预处理。

考虑一些边界:

\(j-k=0\)的时候,此时\(j=k\),代回到最开始发现要\(=1\),或者说一般组合题里面认为\(0^0=1\)

\(j-k=1\)的时候,这个时候简单一点,就是等比数列不能用公式的情况,直接\(=n+1\)即可。

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>


using namespace std;  typedef long long ll;
inline int qr(){
      register int ret=0,f=0;
      register char c=getchar();
      while(c<48||c>57)f|=c==45,c=getchar();
      while(c>=48&&c<=57) ret=ret*10+c-48,c=getchar();
      return f?-ret:ret;
}

namespace poly{
      const int maxn=1<<21|1;
      int r[maxn];
      int savlen;
      inline void getr(const int&len){
        if(len==savlen) return;
        int cnt=0;
        for(register int t=1;t<len;t<<=1)++cnt;
        for(register int t=1;t<len;++t)
          r[t]=r[t>>1]>>1|(t&1)<<cnt>>1;
      }
      const int mod=998244353;
      const int g=3;
      const int gi=332748118;
      inline int ksm(const int&base,const int&p){
        register int ret=1;
        for(register int t=p,b=base%mod;t;t>>=1,b=1ll*b*b%mod)
          if(t&1) ret=1ll*ret*b%mod;
        return ret;
      }
      inline void NTT(int*a,const int&len,const int&tag){
        getr(len);
        for(register int t=1;t<len;++t)
          if(r[t]>t) swap(a[t],a[r[t]]);
        int *a0,*a1,s=g;
        if(tag!=1) s=gi;
        for(register int t=1,wn;t<len;t<<=1){
          wn=ksm(s,(mod-1)/(t<<1));
          for(register int i=0;i<len;i+=t<<1){
            a1=(a0=a+i)+t;
            for(register int k=0,m,w=1;k<t;++k,++a1,++a0,w=1ll*w*wn%mod){
                  m=1ll*w**a1%mod;
                  *a1=(*a0+mod-m)%mod;
                  *a0=(*a0+m)%mod;
            }
          }
        }
        if(tag!=1)
        for(register int t=0,w=ksm(len,mod-2);t<len;++t)
          a[t]=1ll*a[t]*w%mod;
      }
}

using namespace poly;
int jc[100005],inv[100005],bin[100005],n;

inline void pre(){
      bin[0]=jc[0]=inv[0]=1;
      for(register int t=1;t<=100000;++t)
        jc[t]=1ll*jc[t-1]*t%mod;
      inv[100000]=ksm(jc[100000],mod-2);
      for(register int t=100000-1;t;--t)
        inv[t]=1ll*(t+1)*inv[t+1]%mod;
      for(register int t=1;t<=100000;++t)
        bin[t]=(bin[t-1]<<1)%mod;
      
}


int a[maxn],b[maxn];
int main(){
#ifndef ONLINE_JUDGE
      freopen("in.in","r",stdin);
#endif
      pre();
      n=qr();
      for(register int t=0;t<=n;++t){
        a[t]=inv[t];
        if(t&1) a[t]=mod-a[t];
        b[t]=1ll*(ksm(t,n+1)-1ll+mod)%mod*ksm(t-1,mod-2)%mod*inv[t]%mod;
      }
      b[0]=1;b[1]=n+1;
      int k=1;
      while(k<=n)k<<=1;
      NTT(a,k<<1,1);NTT(b,k<<1,1);
      for(register int t=0;t<k<<1;++t) a[t]=1ll*a[t]*b[t]%mod;
      NTT(a,k<<1,-1);
      int ans=0;
      for(register int t=0;t<=n;++t)
        ans=(ans+1ll*bin[t]*jc[t]%mod*a[t]%mod)%mod;
      cout<<ans<<endl;
      return 0;
}

转载于:https://www.cnblogs.com/winlere/p/11204007.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值