codeforces960G. Bandit Blues

题目链接:codeforces960G

来看看三倍经验:hdu4372 luogu4609

某蒟蒻的关于第一类斯特林数的一点理解QAQ:https://www.cnblogs.com/zhou2003/p/10780832.html

注意到当前序列的最大值会对前缀最大值和后缀最大值均产生\(1\)的贡献

那么当我们去掉这个最大值后,剩下\(n-1\)个元素,需要产生\(a-1\)个前缀最大值和\(b-1\)个后缀最大值,并且它们的位置会以最大值为界限分布在两侧

我们将剩下的\(n-1\)个元素分成\((a-1)+(b-1)\)组,每一组钦定最大值在最开头,那么每一个这样的划分就对应了一个合法的序列,最后答案就是\(S(n-1,a+b-2)*\dbinom{a+b-2}{a-1}\),其中\(S(n,m)\)表示第一类斯特林数

这样你就可以完成后两题了

那么对于第一题呢?我们有这个式子:
\[ S(n,m)=[x^m]\prod_{i=0}^{n-1}(x+i) \]
于是分治+FFT求之,据说有\(O(nlogn)\)的倍增做法?我懒啊

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 998244353
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
int n,a,b,rev[400400];
ll s[20][400400];

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

ll qpow(ll x,int y)
{
    ll ans=1;
    while (y)
    {
        if (y&1) ans=(ans*x)%maxd;
        x=(x*x)%maxd;
        y>>=1;
    }
    return ans;
}

ll inv(ll x) {return qpow(x,maxd-2);}

ll C(int n,int m)
{
    if (n<m) return 0;
    ll ans=1;
    per(i,n,n-m+1) ans=ans*i%maxd;
    rep(i,1,m) ans=ans*inv(i)%maxd;
    return ans;
}

void ntt(int lim,ll *a,int typ)
{
    rep(i,0,lim-1)
        if (i<rev[i]) swap(a[i],a[rev[i]]);
    int mid;
    for (mid=1;mid<lim;mid<<=1)
    {
        ll wn=qpow(3,(maxd-1)/(mid<<1));
        int len=(mid<<1),sta,j;
        if (typ==-1) wn=inv(wn);
        for (sta=0;sta<lim;sta+=len)
        {
            ll w=1;
            for (j=0;j<mid;j++,w=(w*wn)%maxd)
            {
                ll x=a[sta+j],y=a[sta+j+mid]*w%maxd;
                a[sta+j]=(x+y)%maxd;
                a[sta+j+mid]=(x+maxd-y)%maxd;
            }
        }
    }
    if (typ==-1)
    {
        int invn=inv(lim);
        rep(i,0,lim-1) a[i]=a[i]*invn%maxd;
    }
}

void solve(int l,int r,int d)
{
    if (l==r) {s[d][0]=l;s[d][1]=1;return;}
    int mid=(l+r)>>1;
    solve(l,mid,d+1);
    rep(i,0,mid-l+1) s[d][i]=s[d+1][i];
    solve(mid+1,r,d+1);
    int lim=1,cnt=0;
    while (lim<=(r-l+1)) {lim<<=1;cnt++;}
    rep(i,mid-l+2,lim) s[d][i]=0;
    rep(i,r-mid+1,lim) s[d+1][i]=0;
    rep(i,0,lim-1)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(cnt-1));
    ntt(lim,s[d],1);ntt(lim,s[d+1],1);
    rep(i,0,lim-1) s[d][i]=s[d][i]*s[d+1][i]%maxd;
    ntt(lim,s[d],-1);
}

int main()
{
    n=read();a=read();b=read();
    if ((n-1<a+b-2) || (!a) || (!b)) {printf("0");return 0;}
    if (n==1) {printf("1");return 0;}
    solve(0,n-2,0);
    printf("%lld",C(a+b-2,a-1)*s[0][a+b-2]%maxd);
    return 0;
}

转载于:https://www.cnblogs.com/encodetalker/p/10780541.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值