P8554 心跳

本文探讨了一道关于数列统计与双射构造的算法题。通过将问题转化为颜色序列,利用动态规划方法实现计数,巧妙地解决了原问题。代码展示了具体的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

赫尔德想对上面的问题进行探究,她想先做一些统计,于是她抽象了这个问题。
我们对于一个长为 lll 的数列 ppp,定义函数:

  • f(p)f(p)f(p) 表示有多少 1≤i≤l1\le i\le l1il 满足 pi=max⁡j=1ipjp_i=\max_{j=1}^i p_jpi=maxj=1ipj(即前缀最大值的个数)。
    现在,给定 n,mn,mn,m,请求出有多少满足以下条件的长为 nnn 的,值域在 [m,n][m,n][m,n] 数列 aaa
  • 存在一个排列 ppp 使得:令 PiP_iPi 代表 ppp 去掉 pip_ipi 后的数列(即 [p1,p2,…,pi−1,pi+1,…,pn][p_1,p_2,\dots,p_{i-1},p_{i+1},\dots,p_n][p1,p2,,pi1,pi+1,,pn]),f(Pi)=aif(P_i)=a_if(Pi)=ai
    答案对 109+710^9+7109+7 取模。

n,m≤2000n,m\le2000n,m2000

题解

玄妙双射题
先考虑m=1m=1m=1怎么做
考虑给aaa序列构造一个双射,我们假设kkk为原排列的前缀最大值个数(可以证明一个aaa只会对应一个kkk,所以不会算重)
ai<ka_i<kai<k说明不合法,pip_ipi不是前缀最大值时,ai=ka_i=kai=k,当pip_ipi时前缀最大值的时,ai∈[k−1,n]a_i\in[k-1,n]ai[k1,n]
分析一下,一个序列中只有333种数,一种是前缀最大值称为红色,一种是删除最靠近它的前缀最大之后会变为前缀最大值称为绿色,一种是无论如何都会变成前缀最大值,称为黄色

我们考虑让一个合法序列aaa唯一对应一个颜色序列
我们用以下操作构造颜色序列
1.首先找到ai≠ka_i\not =kai=kiii,把该位置染成红色,在它后面需要ai−ka_i-kaik个绿色,由于它们相对位置并不重要,所以我们钦定后面的绿色紧跟在红色后面,即j∈[i+1,i+ai−k]j\in[i+1,i+a_i-k]j[i+1,i+aik]的位置会被染成绿色
2.假设我们已经钦定了www个位置为红色,显然我们还需要钦定k−wk-wkw个红色位置,我们每次找出最小的iii,使得ai=ai+1=ka_i=a_{i+1}=kai=ai+1=k,并且iiii+1i+1i+1均未被染色,然后把iii染红,i+1i+1i+1染绿,不断进行这个操作k−wk-wkw
3.其余未染色位值均染成黄色

显然一个合法的aaa必定能够对应一个颜色序列,然后一个颜色序列必定能够回推出一个aaa序列,所以合法aaa序列和颜色序列构成双射,且显然对于任意一个颜色序列,我们都可以构造出一个合法的ppp,可以考虑插入构造,所以颜色序列的个数就是合法aaa的个数

直接对颜色序列进行计数
对于颜色序列还有一些隐性限制
1.第一个一定是红,第二个不能为黄
2.x为不为绿的颜色,红绿x前不能出现黄黄,不能出现黄红绿,否则不符合构造颜色序列时的操作2
3.绿紧跟红
根据这三个限制就可以设计状态了,下面是我自己设计的状态

0 黄 无黄黄
1 黄红 无黄黄
2 黄红绿 无黄黄
3 绿 无黄黄 前方不是黄红
4 红 无黄黄 前方非黄
5 黄 有黄黄
6 红 有黄黄
7 红绿 有黄黄
8 绿绿 有黄黄

每行的第二段字表示当前结尾的情况
m>1m>1m>1的情况就要多记两个东西
1.红色的个数
2.是否有红色后面不接绿色
细节挺多

code\text{code}code

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e3+10,mod=1e9+7;
int n,m;
int f[2][9][N+10][N+10];
void Add(int &a,int b){a+=b;if(a>=mod) a-=mod;}
int main()
{
	scanf("%d %d",&n,&m);
	n++;
	f[0][3][1][2]=1;
	f[1][4][2][2]=1;
	for(int i=3;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			for(int k=0;k<=1;k++)
			{
				Add(f[k][0][j][i],f[k][1][j][i-1]),Add(f[k][0][j][i],f[k][3][j][i-1]),Add(f[k][0][j][i],f[k][4][j][i-1]);
				Add(f[k][1][j][i],f[k][0][j-1][i-1]);
				Add(f[k][2][j][i],f[k][1][j][i-1]);
				Add(f[k][3][j][i],f[k][2][j][i-1]),Add(f[k][3][j][i],f[k][3][j][i-1]),Add(f[k][3][j][i],f[k][4][j][i-1]);
				Add(f[k][4][j][i],f[k][1][j-1][i-1]),Add(f[k][4][j][i],f[k][3][j-1][i-1]),Add(f[k][4][j][i],f[k][4][j-1][i-1]);
				Add(f[k][5][j][i],f[k][0][j][i-1]),Add(f[k][5][j][i],f[k][5][j][i-1]),
				Add(f[k][5][j][i],f[k][6][j][i-1]),Add(f[k][5][j][i],f[k][8][j][i-1]);
				Add(f[k][6][j][i],f[k][5][j-1][i-1]),Add(f[k][6][j][i],f[k][6][j-1][i-1]),Add(f[k][6][j][i],f[k][8][j-1][i-1]);
				Add(f[k][7][j][i],f[k][6][j][i-1]);
				Add(f[k][8][j][i],f[k][7][j][i-1]),Add(f[k][8][j][i],f[k][8][j][i-1]);
			}
			Add(f[1][0][j][i],f[0][1][j][i-1]),Add(f[1][0][j][i],f[0][4][j][i-1]);
			Add(f[1][4][j][i],f[0][4][j-1][i-1]),Add(f[1][4][j][i],f[0][1][j-1][i-1]);
			Add(f[1][5][j][i],f[0][6][j][i-1]),Add(f[1][6][j][i],f[0][6][j-1][i-1]);

			Add(f[0][0][j][i],mod-f[0][1][j][i-1]),Add(f[0][0][j][i],mod-f[0][4][j][i-1]);
			Add(f[0][4][j][i],mod-f[0][4][j-1][i-1]),Add(f[0][4][j][i],mod-f[0][1][j-1][i-1]);
			Add(f[0][5][j][i],mod-f[0][6][j][i-1]),Add(f[0][6][j][i],mod-f[0][6][j-1][i-1]);
		}
	int ans=0;
	for(int j=m+1;j<=n;j++)
		for(int k=0;k<=1;k++)
			Add(ans,f[k][0][j][n]),Add(ans,f[k][5][j][n]);
	Add(ans,f[0][0][m][n]),Add(ans,f[0][5][m][n]);
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值