【2019雅礼集训】【第一类斯特林数】【NTT&多项式】permutation

目录

  • 题意
  • 输入格式
  • 输出格式
  • 思路:
  • 代码

题意

找有多少个长度为n的排列,使得从左往右数,有a个元素比之前的所有数字都大,从右往左数,有b个元素比之后的所有数字都大。

n<=2*10^5,a,b<=n

输入格式

输入三个整数n,a,b。

输出格式

输出一个整数,表示答案。

思路:

这道题是真的神啊...

首先,根据官方题解的思路,首先有一个n^2的DP:

定义dp[i][j]表示一个长度为i的排列,从前往后数一共有j个数字大于所有排在它前面的数字。

首先有转移式:

\[dp[i][j]=dp[i-1][j-1]+(i-1)*dp[i-1][j]\]

怎么理解这个式子呢?

首先,最后的排列一定是这样一个形式:

中间那个是最大值(n),那么前面第一个位置到第二个位置之间不能放任意一个数;第二个位置与第三个位置之间能够放1~ 4之间的数;第三个与第四个之间能够放4~9...我们能够发现,相邻两个位置能够放的数一定是小于前一个位置的。那么我们就可以根据选中的关键点(如上图中的1,4,9)将前半部分的序列分为几部分,每一部分的代表元素为这一部分中的数字的最大值。

例如:1-423-95678,就可以看成是3个部分。代表元素分别是1,4,9。

根据定义,现在考虑的是一共有i个数字,分成了j段。考虑加入一个新的最小的数字,考虑它放在哪里:

  1. 放在开头,自己成为一个新的部分,就由dp[i-1][j-1]转移而来。

    2.因为是最小的,所以可以放在之前的所有已经存在的部分中,那么有(i-1)中方案,就由(i-1)*dp[i-1][j]转移而来。

这样子就有了dp的转移式,很显然最后的答案就是:

\[Ans=\sum_{i=1}^{n}(dp[i][a-1]*dp[n-i-1][b-1])*C_{n-1}^{i-1}\]

其实就是枚举最大值的位值i,然后从剩下的n-1中选出i-1个,再将这i-1个数字分为a-1个部分,后面的n-i-1个位置分为b-1个部分。那就有\(O(n^2)\)的算法了。



有了上述的式子之后,接下来就比较好处理了。

仔细观察dp的转移式,我们会惊奇的发现它竟然和第一类斯特林数的递推式是一样的。也就是:

\[dp[i][j]=[^{i}_{j}]\]

第一类斯特林数是将i个数分成j个圆排列的方案数(忽略顺序的前提下)。而我们可以把之前定义的"部分"每一个都看成是一个圆排列,每一个都把其中最大的值通过圆排列转到这一部分开头的位置,就完美的对应上了。

而在全局看,我们可以先将n-1个数字分配当a+b-2个圆排列里面去,然后再将这a+b-2分成左边a-1个和右边b-1个,就是简单组合数了。那么答案式就可以进一步化简为:

\[Ans=[_{a+b-2}^{\ \ n-1}]*C_{a+b-2}^{a-1}\]

这样子我们的主要问题就变为求前面那个斯特林数就可以了。



而如何快速求斯特林数又是另外一个问题了...

感觉这里写一遍的话,好像有些冗长了。就在下面贴了一个链接(还在写:)),在里面我会尽可能详细的讲解如何快速求解S(n,k)。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 600000
#define MO 998244353
#define G 3
using namespace std;
int seq[MAXN+5];
int n,a,b;
int fact[MAXN+5],inv[MAXN+5];
int PowMod(int x,int y)
{
    int ret=1;
    while(y)
    {
        if(y&1)
            ret=1LL*ret*x%MO;
        x=1LL*x*x%MO;
        y>>=1;
    }
    return ret;
}
void Prepare()
{
    fact[0]=1;
    for(int i=1;i<=MAXN;i++)
        fact[i]=1LL*fact[i-1]*i%MO;
    inv[MAXN]=PowMod(fact[MAXN],MO-2);
    for(int i=MAXN-1;i>=0;i--)
        inv[i]=1LL*inv[i+1]*(1LL*i+1LL)%MO;
}
void Reverse(int A[],int deg)
{
    for(int i=0;i<deg/2;i++)
        swap(A[i],A[deg-i-1]);
}
void NTT(int P[],int len,int oper)
{
    for(int i=1,j=0;i<len-1;i++)
    {
        for(int s=len;j^=s>>=1,~j&s;);
        if(i<j) swap(P[i],P[j]);
    }
    int unit,unit_p0;
    for(int d=0;(1<<d)<len;d++)
    {
        int m=(1<<d),m2=m*2;
        unit_p0=PowMod(G,(MO-1)/m2);
        if(oper==-1)
            unit_p0=PowMod(unit_p0,MO-2);
        for(int i=0;i<len;i+=m2)
        {
            unit=1;
            for(int j=0;j<m;j++)
            {
                int &P1=P[i+j+m],&P2=P[i+j];
                int t=1LL*unit*P1%MO;
                P1=((1LL*P2-1LL*t)%MO+MO)%MO;
                P2=(1LL*P2+1LL*t)%MO;
                unit=1LL*unit*unit_p0%MO;
            }
        }
    }
    if(oper==-1)
    {
        int inv=PowMod(len,MO-2);
        for(int i=0;i<len;i++)
            P[i]=1LL*P[i]*inv%MO;
    }
}
void Mul(int ret[],int _x[],int l1,int _y[],int l2)
{
    static int RET[MAXN+5],X[MAXN+5],Y[MAXN+5];
    int len=1;
    while(len<l1+l2)    len<<=1;
    copy(_x,_x+l1,X);copy(_y,_y+l2,Y);
    fill(X+l1,X+len,0);fill(Y+l2,Y+len,0);
    NTT(X,len,1);NTT(Y,len,1);
    for(int i=0;i<len;i++)
        RET[i]=1LL*X[i]*Y[i]%MO;
    NTT(RET,len,-1);
    copy(RET,RET+l1+l2,ret);
}
void Get(int deg,int A[],int B[])
{
    static int tmpA[MAXN+5],tmpB[MAXN+5];
    int len=deg/2;
    for(int i=0;i<len+1;i++)
        tmpA[i]=1LL*PowMod(len,i)*inv[i]%MO;
    fill(tmpA+len+1,tmpA+deg+1,0);
    for(int i=0;i<len+1;i++)
        tmpB[i]=1LL*fact[i]*A[i]%MO;
    fill(tmpB+len+1,tmpB+deg+1,0);
    Reverse(tmpA,len+1);
    Mul(tmpA,tmpA,len+1,tmpB,len+1);
    for(int i=0;i<=len;i++)
        tmpA[i]=1LL*tmpA[i+len]*inv[i]%MO;
    copy(tmpA,tmpA+len+1,B);
}
void Solve(int deg,int B[])
{
    static int tmpB[MAXN+5];
    if(deg==1)
    {
        B[1]=1;
        return;
    }
    Solve(deg/2,B);
    int hf=deg/2;
    copy(B,B+hf+1,tmpB);
    fill(tmpB+hf+1,tmpB+deg+1,0);
    Get(deg-deg%2,tmpB,tmpB+hf+1);
    Mul(B,tmpB,hf+1,tmpB+hf+1,hf+1);
    if(deg%2==1)
        for(int i=deg;i>=1;i--)
            B[i]=(1LL*B[i]*(1LL*deg-1LL)%MO+1LL*B[i-1])%MO;
}
int C(int x,int y)
{
    return 1LL*fact[x]*inv[y]%MO*inv[x-y]%MO;
}
int main()
{
    Prepare();
    scanf("%d %d %d",&n,&a,&b);
    if(n==1&&a==1&&b==1)
//注意加特判,也可以通过改变Solve底层的返回条件来兼容这种情况
        printf("1\n");
    else if((n>1&&a==1&&b==1)||a==0||b==0)
        printf("0\n");
    else
    {
        Solve(n-1,seq);//快速求第一类斯特林数(nlogn)
        int part1=seq[a+b-2];
        int ans=1LL*C(a+b-2,a-1)*part1%MO;
        printf("%d\n",ans);
    }
    return 0;
}
/*
2 2 1

5 2 2

*/

原文地址:https://www.cnblogs.com/T-Y-P-E/p/10258958.html

时间: 2024-07-31 19:43:24

【2019雅礼集训】【第一类斯特林数】【NTT&多项式】permutation的相关文章

2019雅礼集训 D4T1 w [费用流]

题目描述: 样例: input1: 4 1 2 1 2 3 4 1 2 1 3 3 4 1 2 2 3 1 4 2 1 3 4 1 1 2 3 output1: 9 input2: 5 1 1 3 99 99 100 2 1 2 1 3 3 4 3 5 1 3 1 2 2 4 2 5 2 1 2 3 1 2 1 2 2 1 output2: 198 数据范围: 先放个原题地址:CF1061E. 毒瘤出题人搬原题差评 毒瘤出题人题目翻译出锅差评 这题看到如此不伦不类的问法,似乎不是dp.贪心等算法

2019雅礼集训 D7T1 inverse [概率/期望,DP]

题目描述: 样例: input1: 3 1 1 2 3 output1: 833333340 input2: 5 10 2 4 1 3 5 output2: 62258360 数据范围与约定: 概率/期望的常用套路:将许许多多个元素单独考虑,以达到解决问题的目的. 这里发现不可能整个序列一起考虑,于是枚举任意两个位置,计算出k次翻转之后左边大于右边的概率,再加起来就好了. 于是我们有了一个非常暴力的DP: 令\(dp(i,j,k)?\) 表示k次翻转之后i位置大于j位置的概率.为了方便我们强行令

2019雅礼集训 D7T2 subsequence [DP,平衡树]

题目描述: 样例: input1: 5 -2 -8 0 5 -3 output1: 5 10 13 2 -13 input2: 6 -10 20 -30 40 -50 60 output2: 60 160 280 390 400 210 数据范围与约定: 考虑DP:令\(dp(i,j)\)表示前\(i\)个点选\(j\)个,能得到的最大价值. 得到转移方程:\(dp(i,j)=\max\{dp(i-1,j),dp(i-1,j-1)+a_i\cdot j\}\) 这个方程很明显是\(n^2\)的.

2019雅礼集训 D10T2 硬币翻转 [交互题]

题目描述: coin.h: #include<string> void guess(); int ask(std::string coin); grader.cpp: #include "coin.h" #include <iostream> #include <assert.h> using namespace std; namespace U { using u64 = unsigned long long; using i64 = long l

2019雅礼集训 D10T1 数字重排 [DP]

题目描述: 样例: input: 5 5 5 10 17 23 output: 3 数据范围与约定: 简单DP,不做解释,直接搬题解. 标程: #include<bits/stdc++.h> using namespace std; const int N=1e5+5; int n,a[N],m,s; bitset<N>f,g; int main(){ freopen("sort.in","r",stdin); freopen("s

HDU3625(SummerTrainingDay05-N 第一类斯特林数)

Examining the Rooms Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1661    Accepted Submission(s): 1015 Problem Description A murder happened in the hotel. As the best detective in the town, yo

hdu 3625 第一类斯特林数

题目链接:click here Examining the Rooms Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 1146    Accepted Submission(s): 689 Problem Description A murder happened in the hotel. As the best detective

#6030. 【雅礼集训 2017 Day1】矩阵

#6030. 「雅礼集训 2017 Day1」矩阵 题目描述 有一个 n×n  的矩阵,每个位置 (i,j) 如果是 . 表示为白色,如果是 # 表示为黑色. 初始时,每个位置可以是黑色或白色的,(i,j)  位置的值会作为 ai,j 给你. 现在有一种操作,选择两个整数 i,j∈[1,n],记 (i,1),(i,2),…,(i,n) (i, 1), (i, 2)的颜色为 C1,C2,…Cn ??,将 (1,j),(2,j),…,(n,j)  的颜色赋为 C1,C2,…,Cn ??. 你的任务是

【BZOJ】4555: [Tjoi2016&amp;Heoi2016]求和 排列组合+多项式求逆 或 斯特林数+NTT

[题意]给定n,求Σi=0~nΣj=1~i s(i,j)*2^j*j!,n<=10^5. [算法]生成函数+排列组合+多项式求逆 [题解]参考: [BZOJ4555][Tjoi2016&Heoi2016]求和-NTT-多项式求逆 $ans=\sum_{i=0}^{n}\sum_{j=0}^{i}s(i,j)*2^j*j!$ 令$g(n)=\sum_{j=0}^{n}s(n,j)*2^j*j!$ 则ans是Σg(i),只要计算出g(i)的生成函数就可以统计答案. g(n)可以理解为将n个数划分