不妨从必要条件入手,如果ai>ai+1a_i>a_{i+1}ai>ai+1那么显然iii和i+1i+1i+1在同一块中,那么不妨做一个工作,按前缀max\maxmax将结果序列进行划分。
然后有一个神必的结论:c1≥c2c_1\ge c_2c1≥c2(或者c2+c3≤nc_2+c_3\le nc2+c3≤n),其中cic_ici表示长度为iii的段的数目。证明考虑,将结果序列划分成nnn个子序列,用pi,jp_{i,j}pi,j表示第iii个子序列第jjj个数的位置,那么pi,1,pi,2,pi,3p_{i,1},p_{i,2},p_{i,3}pi,1,pi,2,pi,3递增并且对于j=2,3j=2,3j=2,3,满足(pi,j−1,pi,j)(p_{i,j-1},p_{i,j})(pi,j−1,pi,j)之间的数均比api,ja_{p_{i,j}}api,j小。显然划分的每一组的第一个数都是前缀最大值,因此构造是平凡的。那么方案数就是不同的划分数目。复杂度O(n2)O(n^2)O(n2)。
#include<bits/stdc++.h>
#define pb push_back
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int N=6e3+5;
int n,mod;
int f[N][N];
void add(int &x,int y){
if((x+=y)>=mod)x-=mod;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>mod;f[3*n][0]=1;
for(int i=3*n;i>0;i--){
for(int j=0;j<=n;j++){
if(f[i][j]){
add(f[i-1][j],f[i][j]);
if(i>=2)add(f[i-2][j+1],(ll)f[i][j]*(i-1)%mod);
if(i>=3)add(f[i-3][j+1],(ll)f[i][j]*(i-1)%mod*(i-2)%mod);
}
}
}int res(0);
for(int i=0;i<=n;i++)add(res,f[0][i]);
cout<<res;
}

博客围绕算法展开,从必要条件出发,介绍按前缀max对结果序列进行划分的方法。给出一个结论,即长度为i的段的数目满足一定关系,并进行了证明。最后指出方案数为不同的划分数目,复杂度为O(n2)。
8395

被折叠的 条评论
为什么被折叠?



