题解:CF1988F Heartbeat
CF1988F 题解
本题解参考官方做法
题面
题意
按如下方式定义一个排列 \(p_{1\sim n}\) 的权值:
- 定义前缀最大值 \(p_i\) 满足 \(\begin{aligned}p_i=\max^{i}_{j=1}p_j\end{aligned}\),记 \(p\) 的前缀最大值的个数为 \(x\)。
- 定义后缀最大值 \(p_i\) 满足 \(\begin{aligned}p_i=\max^{n}_{j=i}p_j\end{aligned}\),记 \(p\) 的后缀最大值的个数为 \(y\)。
- 定义 \(\begin{aligned}z=\sum^{i=2}_{n}[p_{i-1}<p_i]\end{aligned}\)。
则排列 \(p\) 的权值为 \(a_x\times b_y\times c_z\)。
其中 \(a,b\) 从 \(1\) 开始输入,\(c\) 从 \(0\) 开始输入。
前置知识
本题要用到组合数,组合数要是在转移方程里面进行暴力计算无疑会增加时间复杂度,所以考虑预处理。
那么组合数预处理其实就是用到这个公式:\(C^m_n=C^m_{n-1}+C^{m-1}_{n-1}\),就开个二维数组预处理即可。
当然,既然是前置知识那就简单的证明一下,其实证明方法有两种,第一种是杨辉三角,第二种暴力拆开,这里使用第二种。
\(\begin{aligned}C^{m-1}_{n-1}+C^{m}_{n-1}&=\frac{(n-1)\times(n-2)\times\cdots\times(n-m+1)}{(m-1)!}+\frac{(n-1)\times(n-2)\times\cdots\times(n-m)}{m!}\\&=\frac{(n-1)\times(n-2)\times\cdots\times(n-m+1)\times(n-m+m)}{m!}\\&=\frac{n\times(n-1)\times(n-2)\times\cdots\times(n-m)}{m!}\\&=C^{m}_{n}\end{aligned}\)
思路
首先看到题目很自然的想到了 DP,观察题目发现这题对答案有影响的就是前后缀最大值和上升点(后一个点比前一个点大即满足条件三的点对)的个数。
于是设 \(f_{n,i,j}\) 表示 \(1\sim n\) 的所有排列中有 \(i\) 个前缀最大值,\(j\) 个上升点的排列个数,\(g_{n,i,j}\) 表示 \(1\sim n\) 的所有排列中有 \(i\) 个后缀最大值,\(j\) 个上升点的排列个数。
由对称性可知,\(g_{n,i,j}=f_{b,i,n-j-1}\),于是在打代码的时候就不需要 \(g\),可以将它转化为 \(f\)。
接下来考虑 \(f\) 的转移方程式。
首先先思考如何从 \(n-1\) 个数转变成 \(n\) 个数是比较方便列式子的,容易想到,可以是将之前旧的 \(1\sim n-1\) 的排列中的每一个数都加一,于是就空出了一个 \(1\) 的位置,因为原数列都加上了一,相对大小关系不变,所以只用考虑插入这个新的 \(1\) 会产生的影响即可。
- \(1\) 在上升点对的的中间或者在最后,因为 \(1\) 是最小的,所以这两种情况均不影响前缀最大值和上升点对数(\(1\) 和原本的上升点对中大的数组成一对,但拆散了原本的那一对,所以不影响),于是就有 \(f_{n,i,j}\gets f_{n,i,j}+f_{n-1,i,j}\times(j+1)\)。(\(j\) 是上升点的个数,\(1\) 是放在最后一个的情况。)
- \(1\) 在最前面的位置,这样的话 \(1\) 会添加一个本身的前缀最大值,以及和原本数列的第一个数成为一个上升点对,(因为 \(1\) 是最小的。)于是 \(f_{n,i+1,j+1}\gets f_{n,i+1,j+1}+f_{n-1,i,j}\)。
- \(1\) 在其他的 \(n-j-2\) 个位置的之一,因为 \(1\) 是最小的,所以 \(1\) 放在其他位置不会产生新的前缀最大值,会和插在的位置后面的那一个数产生一个新的上升点对,于是 \(f_{n,i,j+1}\gets f_{n,i,j+1}+f_{n-1,i,j}\times(n-j-2)\)。
如果考虑插入 \(n\) 的话可能也可以,本人没试过,欢迎各位大佬尝试。
当然,这样子的是远远不足以通过题目的,也配不上这黑的难度,\(n^3\) 的空间复杂度接受不了,不过发现三个转移方程的第一维都只与上一个状态有关,于是就考虑使用滚动数组来优化空间,这样空间复杂度就可以接受了。
接下来考虑如何统计答案。
首先想到 \(n\) 是整个数列中最大的数,必定会产生新的前后缀最大值,且继续往 \(n\) 的两边遍历是不会再出现前后缀最大值,所以考虑将数列分成 \(n\) 左边的一段,和 \(n\) 右边的一段。于是就可以得到如下方程。
\(\begin{aligned}ans_n=\sum_{p=1}^{n}\sum_{i=0}^{p-1}\sum_{j=0}^{n-p}\sum_{x=0}^{p-1}\sum_{y=0}^{n-p}C^{p-1}_{n-1}\times f_{p-1,i,x}\times g_{n-p,j,y}\times a_{i+1}\times b_{j+1}\times c_{x+y+[p>1]}\end{aligned}\)。
先来解释下,其实就是枚举 \(n\) 的一边的数字的情况(自然另一边也就出来了),再乘以相应的方案数,当然,如果 \(n\) 在 \(1\) 这个位置不产生的上升点对,所以只有不在 \(1\) 时才会产生上升点对。
这个时间复杂度肯定是不够的,所以考虑降维。
发现 \(f_{p-1,i,x},g_{n-p,j,y}\) 和 \(a_{i+1},b_{j+1}\) 分别配对只有三个不同的变量,于是就可以预处理 \(\begin{aligned}u_{i,x}=\sum^{n}_{p=1}f_{p-1,i,x}\times a_{i+1},v_{j,y}=\sum^{n}_{p=1}g_{n-p,j,y}\times b_{j+1}\end{aligned}\)。
(为什么是三个呢?因为预处理的时间复杂度最多是 \(n^3\) 的,而两个不同的找不到。)
于是方程就变为 \(\begin{aligned}ans_n=\sum_{p=1}^{n}\sum_{x=0}^{p-1}\sum_{y=0}^{n-p}C^{p-1}_{n-1}\times u_{p-1,x}\times v_{n-p,y}\times c_{x+y+[p>1]}\end{aligned}\)。
但是这还是不够的,因为题目要求的是长度小于等于 \(n\) 的所有排列,外面还得套上一个枚举 \(n\) 的长度,所以还得再降一维。
观察发现 \(u_{p-1,x}\) 和 \(c_{x+y+[p>1]}\) 只有三个不同的变量,也有可以预处理啦!将他们的积存进 \(w_{p-1,y}\) 中,于是,最终得到了一个真正正确的式子。
\(\begin{aligned}ans_n=\sum_{p=1}^{n}\sum_{y=0}^{n-p}C^{p-1}_{n-1}\times w_{p-1,y}\times v_{n-p,y}\end{aligned}\)。
上代码!!!
代码
#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;
}

浙公网安备 33010602011771号