P8554 心跳 解题报告
P8554 心跳 解题报告:
题意
定义一个排列上的函数 \(f(p,i)\) 表示序列 \(p\) 去除 \(i\) 位置后前缀最大值个数。对于所有长度为 \(n\) 的排列 \(p\),生成序列 \(a_i=f(p,i)\)。试计数值域 \([m,n]\),且能被生成的 \(a\) 序列数量。
\(1\leqslant m\leqslant n\leqslant 2000\)。
分析
hzr 场切了,有点恐怖的。
下面记 \(g(p)\) 为不去除任何位置的前缀最大值个数。
首先有一个结论,对于两个 \(g\) 不相等的排列,其生成的 \(a\) 序列不可能有交。
经过初步地观察,我们能得知:
- 若去除的位置不是前缀最大值,\(f(p,i)=g(p)\);
- 否则,\(f(p,i)\) 为 \(i\) 后面大于 \([1,i-1]\) 所有数的单调栈大小加 \(g(p)-1\)。
于是我们可以将序列里的数分成三个部分:前缀最大值,可能成为前缀最大值的数(下文称其为有用的数),其他。
将排列按照前缀最大值划分成若干段,那么前缀最大值的 \(f(p,i)\) 就是其对应段有用的数数量加上 \(g(p)-1\)。
我们并不关心有用的数的位置,只关心其数量,于是可以将一段内所有有用的数保留顺序提前,然后生成一个仅包含 \(\{0,1,2\}\) 的序列,\(0\) 对应前缀最大值,\(1\) 对应有用的数,\(2\) 对应没用的数。(称这种序列为颜色序列)
我们想直接 dp 来计数颜色序列,但必须先知道任意颜色序列是否都存在一个对应的排列 \(p\)。
考虑一种排列的生成方式:从前往后加入数字,维护目前前缀 \([1,i]\) 离散化后的结果,每次插入一个 \([1,i+1]\) 内的数字并将大于等于其的数字加一(其实就是在值域上插入数字)。那么插入 \(i+1\) 就对应插入一个 \(0\),插入 \(i\) 就对应插入一个 \(1\),否则就是插入一个 \(2\)。
只需保证第一个位置是 \(0\),第二个位置是 \(0/1\) 即可。
但是不同的颜色序列可能对应相同的 \(a\) 序列,算重的原因实际上就是 \(201\text{x}\) 与 \(012\text{x}\) 等价,以及 \(01\text{x}\) 与 \(22\) 等价(\(\text{x}=\{0,2\}\)),于是我们只需保证序列中不存在 \(201\text{x}\) 子段与 \(22\) 后的 \(01\text{x}\) 子段即可。
令 \(f_{i,j,0/1/2/3,0/1/2}\) 表示 dp 了 \(i\) 个位置,前缀最大值个数为 \(j\),上一个为 \(0\)、\(1\)(长度 \(=1\))、\(1\)(长度 \(>1\))、\(2\),无限制、当前段不能只有 \(1\) 个 \(1\),后面所有段不能只有 \(1\) 个 \(1\),直接转移即可。
\(m\) 的限制很好处理,要么前缀最大值数量大于 \(m\),要么前缀最大值数量等于 \(m\) 且没有一个前缀最大值段没有有意义的数,随便改改转移方程就好了。
复杂度 \(O(n^2)\)。
代码
写不动,借鉴了官方题解的波特状态。
#include<stdio.h>
#include<string.h>
const int maxn=2005,mod=1000000007;
int n,m,ans,now;
int f[2][maxn][11],g[11],h[11];
void solve(int typ){
now=0,f[0][1][1]=1;
if(typ==0)
f[0][2][0]=1;
for(int i=3;i<=n;i++){
now^=1,memset(f[now],0,sizeof(f[now]));
for(int j=1;j<=i;j++){
for(int c=0;c<=10;c++){
g[c]=f[now^1][j][c];
if(typ&&(c==0||c==5||c==8))
g[c]=0;
}
h[0]=h[3]=(0ll+g[0]+g[1]+g[2]+g[8]+g[10])%mod;
h[1]=f[now^1][j][0],h[2]=(g[1]+g[2])%mod;
h[4]=(0ll+g[3]+g[4]+g[5]+g[7])%mod;
h[5]=(0ll+g[4]+g[5]+g[7])%mod;
h[6]=f[now^1][j][5],h[7]=(g[6]+g[7])%mod;
h[8]=g[3];
h[9]=f[now^1][j][8],h[10]=(g[9]+g[10])%mod;
for(int c=0;c<=10;c++){
int nxt=j+(c==0||c==5||c==8);
f[now][nxt][c]=(f[now][nxt][c]+h[c])%mod,h[c]=0;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
solve(0);
for(int i=m+1;i<=n;i++)
for(int c=0;c<=10;c++)
if(c!=6&&c!=9)
ans=(ans+f[now][i][c])%mod;
memset(f,0,sizeof(f));
solve(1);
for(int c=0;c<=10;c++)
if(c!=0&&c!=5&&c!=6&&c!=8&&c!=9)
ans=(ans+f[now][m][c])%mod;
printf("%d",ans);
return 0;
}