题解:CF1988F Heartbeat
CF1988F 题解
本题解参考官方做法
题面
题意
按如下方式定义一个排列 p1∼n 的权值:
- 定义前缀最大值 pi 满足 pi=imaxj=1pj,记 p 的前缀最大值的个数为 x。
- 定义后缀最大值 pi 满足 pi=nmaxj=ipj,记 p 的后缀最大值的个数为 y。
- 定义 z=i=2∑n[pi−1<pi]。
则排列 p 的权值为 ax×by×cz。
其中 a,b 从 1 开始输入,c 从 0 开始输入。
前置知识
本题要用到组合数,组合数要是在转移方程里面进行暴力计算无疑会增加时间复杂度,所以考虑预处理。
那么组合数预处理其实就是用到这个公式:Cmn=Cmn−1+Cm−1n−1,就开个二维数组预处理即可。
当然,既然是前置知识那就简单的证明一下,其实证明方法有两种,第一种是杨辉三角,第二种暴力拆开,这里使用第二种。
Cm−1n−1+Cmn−1=(n−1)×(n−2)×⋯×(n−m+1)(m−1)!+(n−1)×(n−2)×⋯×(n−m)m!=(n−1)×(n−2)×⋯×(n−m+1)×(n−m+m)m!=n×(n−1)×(n−2)×⋯×(n−m)m!=Cmn
思路
首先看到题目很自然的想到了 DP,观察题目发现这题对答案有影响的就是前后缀最大值和上升点(后一个点比前一个点大即满足条件三的点对)的个数。
于是设 fn,i,j 表示 1∼n 的所有排列中有 i 个前缀最大值,j 个上升点的排列个数,gn,i,j 表示 1∼n 的所有排列中有 i 个后缀最大值,j 个上升点的排列个数。
由对称性可知,gn,i,j=fb,i,n−j−1,于是在打代码的时候就不需要 g,可以将它转化为 f。
接下来考虑 f 的转移方程式。
首先先思考如何从 n−1 个数转变成 n 个数是比较方便列式子的,容易想到,可以是将之前旧的 1∼n−1 的排列中的每一个数都加一,于是就空出了一个 1 的位置,因为原数列都加上了一,相对大小关系不变,所以只用考虑插入这个新的 1 会产生的影响即可。
- 1 在上升点对的的中间或者在最后,因为 1 是最小的,所以这两种情况均不影响前缀最大值和上升点对数(1 和原本的上升点对中大的数组成一对,但拆散了原本的那一对,所以不影响),于是就有 fn,i,j←fn,i,j+fn−1,i,j×(j+1)。(j 是上升点的个数,1 是放在最后一个的情况。)
- 1 在最前面的位置,这样的话 1 会添加一个本身的前缀最大值,以及和原本数列的第一个数成为一个上升点对,(因为 1 是最小的。)于是 fn,i+1,j+1←fn,i+1,j+1+fn−1,i,j。
- 1 在其他的 n−j−2 个位置的之一,因为 1 是最小的,所以 1 放在其他位置不会产生新的前缀最大值,会和插在的位置后面的那一个数产生一个新的上升点对,于是 fn,i,j+1←fn,i,j+1+fn−1,i,j×(n−j−2)。
如果考虑插入 n 的话可能也可以,本人没试过,欢迎各位大佬尝试。
当然,这样子的是远远不足以通过题目的,也配不上这黑的难度,n3 的空间复杂度接受不了,不过发现三个转移方程的第一维都只与上一个状态有关,于是就考虑使用滚动数组来优化空间,这样空间复杂度就可以接受了。
接下来考虑如何统计答案。
首先想到 n 是整个数列中最大的数,必定会产生新的前后缀最大值,且继续往 n 的两边遍历是不会再出现前后缀最大值,所以考虑将数列分成 n 左边的一段,和 n 右边的一段。于是就可以得到如下方程。
ansn=n∑p=1p−1∑i=0n−p∑j=0p−1∑x=0n−p∑y=0Cp−1n−1×fp−1,i,x×gn−p,j,y×ai+1×bj+1×cx+y+[p>1]。
先来解释下,其实就是枚举 n 的一边的数字的情况(自然另一边也就出来了),再乘以相应的方案数,当然,如果 n 在 1 这个位置不产生的上升点对,所以只有不在 1 时才会产生上升点对。
这个时间复杂度肯定是不够的,所以考虑降维。
发现 fp−1,i,x,gn−p,j,y 和 ai+1,bj+1 分别配对只有三个不同的变量,于是就可以预处理 ui,x=n∑p=1fp−1,i,x×ai+1,vj,y=n∑p=1gn−p,j,y×bj+1。
(为什么是三个呢?因为预处理的时间复杂度最多是 n3 的,而两个不同的找不到。)
于是方程就变为 ansn=n∑p=1p−1∑x=0n−p∑y=0Cp−1n−1×up−1,x×vn−p,y×cx+y+[p>1]。
但是这还是不够的,因为题目要求的是长度小于等于 n 的所有排列,外面还得套上一个枚举 n 的长度,所以还得再降一维。
观察发现 up−1,x 和 cx+y+[p>1] 只有三个不同的变量,也有可以预处理啦!将他们的积存进 wp−1,y 中,于是,最终得到了一个真正正确的式子。
ansn=n∑p=1n−p∑y=0Cp−1n−1×wp−1,y×vn−p,y。
上代码!!!
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
#define ni i&1
#define li (i-1)&1
using namespace std;
const int MN=705;
const int mod=998244353;
ll n,ans,a[MN],b[MN],c[MN],C[MN][MN],f[2][MN][MN],u[MN][MN],v[MN][MN],w[MN][MN];
void write(ll n){if(n<0){putchar('-');write(-n);return;}if(n>9)write(n/10);putchar(n%10+'0');}
ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
int main(){
// freopen("heart.in","r",stdin);
// freopen("heart.out","w",stdout);
n=read();
for(int i=1; i<=n; i++) a[i]=read();
for(int i=1; i<=n; i++) b[i]=read();
for(int i=0; i<n; i++) c[i]=read();
for(int i=0; i<=n; i++){C[i][0]=1;for(int j=1; j<=i; j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;}
u[0][0]=a[1];v[0][0]=b[1];
for(int i=1; i<=n; i++){
if(i==1) f[1][1][0]=1;
else{
for(int j=1; j<i; j++) for(int k=0; k<i; k++) f[ni][j][k]=0;
for(int j=1; j<i; j++) for(int k=0; k<i-1; k++){
f[ni][j][k]=(f[ni][j][k]+f[li][j][k]*(k+1)%mod)%mod;
f[ni][j+1][k+1]=(f[ni][j+1][k+1]+f[li][j][k])%mod;
f[ni][j][k+1]=(f[ni][j][k+1]+f[li][j][k]*(i-k-2)%mod)%mod;
}
}
for(int j=1; j<=i; j++) for(int k=0; k<i; k++){
u[i][k]=(u[i][k]+f[ni][j][k]*a[j+1]%mod)%mod;
v[i][k]=(v[i][k]+f[ni][j][i-k-1]*b[j+1]%mod)%mod;
}
}
for(int i=0; i<=n; i++) for(int j=0; j<=i; j++) for(int k=0; k<n-j; k++) w[i][k]=(w[i][k]+u[i][j]*c[j+k+(i>0)]%mod)%mod;
for(int i=1; i<=n; i++){
ans=0;
for(int p=1; p<=i; p++) for(int y=0; y<=i-p; y++) ans=(ans+w[p-1][y]*v[i-p][y]%mod*C[i-1][p-1]%mod)%mod;
write(ans);putchar('\n');
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!