P8554-心跳【dp】
前言
虽然退役了还是能写题解,因为是退役前验的题。
题解也是退役前写的,现在也忘了怎么做了。
正题
题目链接:https://www.luogu.com.cn/problem/P8554
题目大意
定义\(f(p)\)表示数列\(p\)的前缀最大值个数。
定义\(p_i\)表示将排列\(p\)中的第\(i\)个去掉后的排列。
现在问你有多少个长度为\(n\)数列\(a\)满足:
- 存在一个排列\(p\)满足\(a_i=f(p_i)\)
- \(\forall a_i,a_i\geq m\)
\(1\leq m<n\leq 2000\)
解题思路
先不考虑 \(m\) 的限制。
我们考虑前缀最大值的个数,假设其为 \(k\) 。当我们修改一个非前缀最大值时 \(f(p)=k\) ,当我们修改一个前缀最大值时,假设这个前缀最大值与下一个前缀最大值之间有 \(len\) 个数,那么 \(f(p)\in[k-1,k-1+len]\) 。
然后限制是要求至少有 \(n-k\) 个数值为 \(k\) ,我们考虑使用 \(dp\) 来计数,一个朴素的想法是设 \(f_{i,j}\) 表示现在计数到第 \(i\) 个,目前已经钦定了 \(j\) 个位置作为前缀最大值。
那么就有转移方程
这个可以转移方程用前缀和优化,但是这样显然会重复计数,因为值为 \(k\) 的位置既可以作为前缀最大值也可以不作为前缀最大值。
那假设对于一个序列 \(a\) 我们要选够 \(k\) 个位置作为前缀最大值,要让选择的方式唯一那么我们一个不错的想法是能尽量往前选就尽量往前选。
换而言之我们倒过来 \(dp\) ,然后把过程 \(dp\) 过程分为两个部分,前一个部分中所有的 \(a_i=k\) 的值的位置都不作为前缀最大值,后一个部分中尽量选取最多的 \(a_i=k\) 的部分作为前缀最大值。
然后后一个部分可以从前一个部分中转移过来就好了。
实现起来细节比较多。
然后考虑 \(m\) 的限制也很简单,最后我们要求选择的前缀最大值的数量不小于 \(m\) 即可。需要注意的是统计 \(k=m\) 的情况时,前面 \(f(p)\in[k-1,k-1+len]\) 这个条件就不适用了,我们应该改成 \(f(p)\in[k,k-1+len]\) 作为条件再进行一次类似的 \(dp\) 。
时间复杂度:\(O(n^2)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=2100,P=1e9+7;
ll n,m,f[N][N],sf[N][N],s[N][N],g[N][N],ans[N];
signed main()
{
scanf("%lld%lld",&n,&m);f[0][n+1]=1;
for(ll i=0;i<=n+1;i++)sf[0][i]=1;
for(ll i=1;i<=n;i++){
ll sum=0;
for(ll j=n;j>=1;j--){
if(j!=1)(sum+=sf[i-1][j+2])%=P;
f[i][j]=(f[i-1][j+1]+sum)%P;
sf[i][j]=(sf[i][j+1]+f[i][j])%P;
}
}
for(ll i=1;i<=n;i++){
ll sum=0,buf=0;
for(ll j=n;j>=1;j--){
buf=(buf+f[i-1][j+2])%P;
s[i][j]=(s[i-1][j+2]+buf)%P;
g[i][j]=(g[i-1][j+2]+g[i-1][j+1]+s[i-1][j+1])%P;
if(j!=1)(g[i][j]+=g[i-1][j+2])%=P;
(sum+=s[i-1][j+3]+g[i-1][j+3]+g[i-1][j+4])%=P;
(g[i][j]+=sum)%=P;
}
}
ll sum=0;
for(ll i=m+1;i<=n;i++){
ans[i]=(f[i][1]+g[i][1]+s[i][1])%P;
(ans[i]+=g[i-1][4]+g[i-2][5])%=P;
(sum+=ans[i])%=P;
// printf("%d\n",ans[i]);
}
if(m!=0){
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
memset(s,0,sizeof(s));
memset(sf,0,sizeof(sf));
memset(ans,0,sizeof(ans));
f[0][n+1]=1;
for(ll i=0;i<=n+1;i++)sf[0][i]=1;
for(ll i=1;i<=n;i++){
ll sum=0;
for(ll j=n;j>=1;j--){
(sum+=sf[i-1][j+3])%=P;f[i][j]=sum;
sf[i][j]=(sf[i][j+1]+f[i][j])%P;
}
}
for(ll i=1;i<=n;i++){
ll sum=0,buf=0;
for(ll j=n;j>=1;j--){
buf=(buf+f[i-1][j+2])%P;
s[i][j]=(s[i-1][j+2]+buf)%P;
(sum+=s[i-1][j+3]+g[i-1][j+3]+g[i-1][j+4])%=P;
g[i][j]=(g[i-1][j+2]+sum)%P;
}
}
sum=(sum+f[m][1]+g[m][1]+s[m][1]+g[m-1][4])%P;
}
printf("%lld\n",sum);
return 0;
}