P8554-心跳【dp】

0|1前言

虽然退役了还是能写题解,因为是退役前验的题。

题解也是退役前写的,现在也忘了怎么做了。


1|0正题

题目链接:https://www.luogu.com.cn/problem/P8554


1|1题目大意

定义f(p)表示数列p的前缀最大值个数。

定义pi表示将排列p中的第i个去掉后的排列。

现在问你有多少个长度为n数列a满足:

  • 存在一个排列p满足ai=f(pi)
  • ai,aim

1m<n2000


1|2解题思路

先不考虑 m 的限制。

我们考虑前缀最大值的个数,假设其为 k 。当我们修改一个非前缀最大值时 f(p)=k ,当我们修改一个前缀最大值时,假设这个前缀最大值与下一个前缀最大值之间有 len 个数,那么 f(p)[k1,k1+len]

然后限制是要求至少有 nk 个数值为 k ,我们考虑使用 dp 来计数,一个朴素的想法是设 fi,j 表示现在计数到第 i 个,目前已经钦定了 j 个位置作为前缀最大值。

那么就有转移方程

fi,j=k=1ifk,j1×(jk)

这个可以转移方程用前缀和优化,但是这样显然会重复计数,因为值为 k 的位置既可以作为前缀最大值也可以不作为前缀最大值。

那假设对于一个序列 a 我们要选够 k 个位置作为前缀最大值,要让选择的方式唯一那么我们一个不错的想法是能尽量往前选就尽量往前选。

换而言之我们倒过来 dp ,然后把过程 dp 过程分为两个部分,前一个部分中所有的 ai=k 的值的位置都不作为前缀最大值,后一个部分中尽量选取最多的 ai=k 的部分作为前缀最大值。

然后后一个部分可以从前一个部分中转移过来就好了。

实现起来细节比较多。

然后考虑 m 的限制也很简单,最后我们要求选择的前缀最大值的数量不小于 m 即可。需要注意的是统计 k=m 的情况时,前面 f(p)[k1,k1+len] 这个条件就不适用了,我们应该改成 f(p)[k,k1+len] 作为条件再进行一次类似的 dp

时间复杂度:O(n2)


1|3code

#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; }

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/16728121.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(126)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2021-09-25 51nod1667-概率好题【容斥,组合数学】
2021-09-25 CF891C-Envy【可撤销并查集】
2021-09-25 牛客练习赛89E-牛牛小数点【数论】
2021-09-25 P6295-有标号 DAG 计数【多项式求逆,多项式ln】
点击右上角即可分享
微信分享提示