专栏文章

题解:CF1988F Heartbeat

CF1988F题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@mip62sps
此快照首次捕获于
2025/12/03 06:45
3 个月前
此快照最后确认于
2025/12/03 06:45
3 个月前
查看原文

猜测目标

提前声明:本篇文章比较偏向小白因为本作者是蒟蒻。
本题解参考官方做法。
一看到数据范围,容易想到我们需要 O(n3)\mathcal O(n^3)O(n2logn)\mathcal O(n^2 \log n) 的时间复杂度去解决这个问题。
不过看看时限,还是 O(n3)\mathcal O(n^3) 的概率大一点。

设计状态

首先,这一道题和全排列有关,容易想到肯定有一个 ii 表示将 1i1 \sim i 这些数字排列。
但是如果我们如果这么定义:
  • dpi,j,k,xdp_{i,j,k,x} 表示 1i1 \sim i 的排列,jj 个上升点对,kk 个前缀最大值,xx 个后缀最大值的方案数。
显然在没有得到任何性质之前,这根本转移不了啊。
发现问题是无法同时维护前缀最大值和后缀最大值的个数,于是我们只能含泪去掉一个,于是定义:
  • fi,j,kf_{i,j,k} 表示 1i1 \sim i 的排列,jj 个上升点对,kk 个前缀最大值的方案数。
  • gi,j,kg_{i,j,k} 表示 1i1 \sim i 的排列,jj 个上升点对,kk 个后缀最大值的方案数。
因为我们拆开了 dpdp 数组中的 k,xk,x,但是很明显,在一个全排列中总有一对 (k,x)(k,x) 是不可能出现的。
比如当 i=3i=3 时,(1,1)(1,1) 是不可能出现的。
因为如果你的 k=1k=1,那么 33 就必须在第一个位置;如果你的 x=1x=1,那么 33 就必须在第三个位置。矛盾了。
也就是说,k,xk,x 是有隐含的联系的,我们这么拆掉,只是为了方便转移,后面统计答案时要想办法将这两者之间的联系给消掉,否则答案很难统计或者根本统计不了,这个到时候再说吧,先放着。

转移方程

我们先考虑怎么转移。
首先,容易发现 ffgg 是对称的。
具体来说,gi,ij1,k=fi,j,kg_{i,i-j-1,k}=f_{i,j,k}
为什么呢?考虑将 ff 每一个方案都给表示成一个数组。
然后,将这个数组倒过来,这样前缀最大值就变成后缀最大值啦。
考虑上升点对,很明显原来的上升点对变成了下降点对,下降点对变成了上升点对。
但是因为总共只有 i1i-1 个点对,所以翻过来之后就是 ij1i-j-1
所以,我们只要把 ff 求出来就行了。
怎么从 ii+1i \to i+1 呢?我们只要将原来 1i1 \sim i 的排列变成 2i+12 \sim i+1 的排列,然后再插入一个 11 就好啦。
至于为什么不能直接插入 i+1i+1,等会会有解释。
讨论:
  1. 11 插入到 jj 个上升点对之间。
  2. 11 插入到第一个位置。
  3. 11 插入到最后一个位置。
  4. 其他情况。
对于第一种情况,虽然 11 把这个上升点对拆散了,但是 11 和原先上升点对的后面那个数结合起来,又组成了一个新的上升点对,所以最后没变。
所以则有:
dpi,j,k×jdpi+1,j,kdp_{i,j,k} \times j \to dp_{i+1,j,k}
当然,在这个情况下,11 肯定不会插入到第一个位置(否则那就不叫之间了),插入到第一个位置的情况要另外讨论,因为会新增一个前缀最大值。
对于第二种情况,11 仍然会和后面的一个数构成一个新的上升点对,并且新增一个前缀最大值。
所以则有:
dpi,j,kdpi+1,j+1,k+1dp_{i,j,k} \to dp_{i+1,j+1,k+1}
对于第三种情况,既不会产生新的上升点对,也不会产生新的前缀最大值。
所以则有:
dpi,j,kdpi+1,j,kdp_{i,j,k} \to dp_{i+1,j,k}
对于第四种情况,则会产生一个新的上升点对,不会产生新的前缀最大值。
所以则有:
dpi,j,k×(ij1)dpi+1,j+1,kdp_{i,j,k} \times (i-j-1) \to dp_{i+1,j+1,k}
将所有情况讨论完后,总结一下,有如下转移:
{dpi,j,k×(j+1)dpi+1,j,kdpi,j,kdpi+1,j+1,k+1dpi,j,k×(ij1)dpi+1,j+1,k\begin{equation*} \begin{cases} dp_{i,j,k} \times (j+1) \to dp_{i+1,j,k} \\ dp_{i,j,k} \to dp_{i+1,j+1,k+1} \\ dp_{i,j,k} \times (i-j-1) \to dp_{i+1,j+1,k}\\ \end{cases} \end{equation*}
对不能插入 i+1i+1 的一个解释:
  • 首先,对于上升点对来说,插入 i+1i+1 是可计算的。
  • 但是,对于前缀最大值来说,如果在某个位置插入了 i+1i+1,那么就会使得这个位置的后面,原先是前缀最大值的将不是前缀最大值,但是我们无法统计一个位置后面的前缀最大值个数,自然就不能插入 i+1i+1 啦。
  • 就算方法改了之后还是可以插入 i+1i+1,那我估计转移的复杂度应该不是 O(1)\mathcal O(1) 啦,估计最后优化不到正解的程度,而且不如插入 11 简单。
  • 所以这里不讨论插入 i+1i+1 的转移,而且我估计以当前状态应该是很难转移了。
至于空间复杂度,很明显,因为所有式子都是 ii+1i \to i+1,所以直接滚动即可。

统计答案

首先,如讲述“状态定义”标题时已经有提到过,我们要让 k,xk,x 之间的联系断开。
我们想想,前缀最大值和后缀最大值,怎样才能断开呢?
其实,都是最大值嘛,那我们就像前文对不能插入 i+1i+1 的解释一样,插入一个比整个序列更大的数,那不就将 k,xk,x 的联系给切开了嘛?
所以,我们枚举全排列 1n1 \sim nnn 的位置,这样 k,xk,x 就在 nn 位置断开了,就无关了。
于是,答案如下:
ansn=p=1ni=1p1j=1npx=1p1y=1npCn1p1fp1,x,ignp,y,jai+1bj+1cx+y+[p>1]ans_n= \sum_{p=1}^n \sum_{i=1}^{p-1} \sum_{j=1}^{n-p} \sum_{x=1}^{p-1} \sum_{y=1}^{n-p} C_{n-1}^{p-1} \cdot f_{p-1,x,i} \cdot g_{n-p,y,j} \cdot a_{i+1} \cdot b_{j+1} \cdot c_{x+y+[p>1]}
其中,pp 枚举的是 nn 所在的位置,i,ji,j 分别枚举的是前缀最大值和后缀最大值个数,x,yx,y 分别枚举的是 nn 左边的上升点对个数和 nn 右边的上升点对个数。
然后至于 Cn1p1C_{n-1}^{p-1},就是分开之后在 1n11 \sim n-1 这些数中选 p1p-1 个数作为左边的部分。
为什么呢?因为我们其实要的只是相对顺序
至于上升点对,如果 p>1p>1,那么他就会和前一个数组成一个没有被统计的上升点对,但是 p=1p=1 没有前一个数,所以无法组成上升点对。
如果真就这样统计的话,总时间复杂度为 O(n6)\mathcal O(n^6),完全不可接受。
但是,我们观察到有一些式子的部分只与部分变量有关,于是转换,得:
ansn=p=1nx=1p1y=1npi=1p1j=1npCn1p1(fp1,x,iai+1)(gnp,y,jbj+1)cx+y+[p>1]ans_n= \sum_{p=1}^n \sum_{x=1}^{p-1} \sum_{y=1}^{n-p} \sum_{i=1}^{p-1} \sum_{j=1}^{n-p} C_{n-1}^{p-1} \cdot (f_{p-1,x,i} \cdot a_{i+1}) \cdot (g_{n-p,y,j} \cdot b_{j+1}) \cdot c_{x+y+[p>1]}
fp1,x,iai+1,gnp,y,jbj+1,cx+y+[p>1],Cn1p1f_{p-1,x,i} \cdot a_{i+1},g_{n-p,y,j} \cdot b_{j+1},c_{x+y+[p>1]},C_{n-1}^{p-1} 提出去,得:
ansn=p=1n(Cn1p1×x=1p1y=1np(cx+y+[p>1]×(i=1p1fp1,x,iai+1)×(j=1npgnp,y,jbj+1)))ans_n= \sum_{p=1}^n (C_{n-1}^{p-1} \times \sum_{x=1}^{p-1} \sum_{y=1}^{n-p} (c_{x+y+[p>1]} \times (\sum_{i=1}^{p-1} f_{p-1,x,i} \cdot a_{i+1}) \times (\sum_{j=1}^{n-p} g_{n-p,y,j} \cdot b_{j+1})))
于是,我们设:
rp,x=i=1pfp,x,iai+1sp,y=j=1pgp,y,jbj+1r_{p,x}=\sum_{i=1}^p f_{p,x,i} \cdot a_{i+1} \\ s_{p,y}=\sum_{j=1}^p g_{p,y,j} \cdot b_{j+1}
这两个式子的预处理复杂度均为 O(n3)\mathcal O(n^3)
带入原式,得:
ansn=p=1n(Cn1p1×x=1p1y=1np(cx+y+[p>1]×rp1,x×snp,y))ans_n= \sum_{p=1}^n (C_{n-1}^{p-1} \times \sum_{x=1}^{p-1} \sum_{y=1}^{n-p} (c_{x+y+[p>1]} \times r_{p-1,x} \times s_{n-p,y}))
此时时间复杂度被降为 O(n4)\mathcal O(n^4),但还需要再降一个 nn 才能通过本题。
发现此时只要预处理 cx+y+[p>1]×rp1,xc_{x+y+[p>1]} \times r_{p-1,x} 就好了。
为什么不能预处理 rp1,x×snp,yr_{p-1,x} \times s_{n-p,y} 呢?
因为 cx+y+[p>1]c_{x+y+[p>1]} 涉及到了 33 个变量,不管你这个预处理是枚举了 pp 好,还是枚举了 x,yx,y 好,最后你为了计算 cx+y+[p>1]c_{x+y+[p>1]} 还得枚举,还要去重,跟没有预处理一样。
所以,这里预处理的本质是什么?
就是通过变换 \sum 之间的位置,然后将和预处理无关的式子给提出去,只有将所有和预处理无关的式子全部提出去之后,我们才能预处理成功。
否则,如果你提不出去(就像 cx+y+[p>1]c_{x+y+[p>1]}),那你 rp1,x×snp,yr_{p-1,x} \times s_{n-p,y} 也就休想预处理啦。
这样我们就从题目里面又获取到了一个知识啦。
那为什么不能预处理 cx+y+[p>1]×snp,yc_{x+y+[p>1]} \times s_{n-p,y} 呢?
因为你计算 ansans 的时候,nn 也要枚举的啊。这样,如果将 nn 也给算一个变量,那么这个式子就有 44 个变量了,预处理复杂度就升到了 O(n4)\mathcal O(n^4) 了,不如不预处理。
于是,我们还是将上面 ansnans_n 的式子变一下,得:
ansn=p=1n(Cn1p1×y=1npx=1p1(cx+y+[p>1]×rp1,x×snp,y))ans_n= \sum_{p=1}^n (C_{n-1}^{p-1} \times \sum_{y=1}^{n-p} \sum_{x=1}^{p-1} (c_{x+y+[p>1]} \times r_{p-1,x} \times s_{n-p,y}))
snp,ys_{n-p,y} 提出去,得:
ansn=p=1n(Cn1p1×y=1np(snp,y×x=1p1(cx+y+[p>1]×rp1,x)))ans_n= \sum_{p=1}^n (C_{n-1}^{p-1} \times \sum_{y=1}^{n-p} (s_{n-p,y} \times \sum_{x=1}^{p-1} (c_{x+y+[p>1]} \times r_{p-1,x})))
这样,我们就可以愉快的预处理 x=1p1(cx+y+[p>1]×rp1,x)\sum_{x=1}^{p-1} (c_{x+y+[p>1]} \times r_{p-1,x}) 了。
设:
tp,y=x=1p(cx+y+[p>0]×rp,x)t_{p,y}=\sum_{x=1}^p (c_{x+y+[p>0]} \times r_{p,x})
如果不理解,可以将 p=p1p'=p-1 带入,得:
tp,y=x=1p(cx+y+[p>0]×rp,x)tp1,y=x=1p1(cx+y+[p1>0]×rp1,x)t_{p',y}=\sum_{x=1}^{p'} (c_{x+y+[p'>0]} \times r_{p',x}) \\ t_{p-1,y}=\sum_{x=1}^{p-1} (c_{x+y+[p-1>0]} \times r_{p-1,x})
当然,逆着推过来可以知道是否正确,那怎么顺着推过来呢?
p=p1p'=p-1,先书写原来的式子:
tp1,y=x=1p1(cx+y+[p>1]×rp1,x)t_{p-1,y}=\sum_{x=1}^{p-1} (c_{x+y+[p>1]} \times r_{p-1,x})
为了让 cx+y+[p>1]c_{x+y+[p>1]} 能变得和 pp' 有关,于是我们将 p>1p>1 移一下项,得到 p1>0p-1>0,这样就可以转换为 p>0p'>0 了。所以数学还是很重要滴。
这样,我们将 tp,yt_{p,y} 带入原式,得:
ansn=p=1n(Cn1p1×y=1np(snp,y×tp1,y))ans_n= \sum_{p=1}^n (C_{n-1}^{p-1} \times \sum_{y=1}^{n-p} (s_{n-p,y} \times t_{p-1,y}))
这样,再预处理 Cn1p1C_{n-1}^{p-1},这道题就彻底被解决了。
再考虑一下初始值吧,考虑到 p1p-1 可能为 00,所以令 dp0,0,0=dp1,0,1=1dp_{0,0,0}=dp_{1,0,1}=1
很明显,在没有数的时候,肯定啥贡献都没有啊。
至于 dp1,0,1dp_{1,0,1},那应该很明显了吧。
代码:
CPP
#include<bits/stdc++.h>
#define x0 x_0
#define x1 x_1
#define y0 y_0
#define y1 y_1
#define yn y_n
#define j0 j_0
#define j1 j_1
#define jn j_n
#define k0 k_0
#define k1 k_1
#define d0 d_0
#define d1 d_1
#define LL long long
#define LD long double
#define Big __int128
#define STR string
#define US unsigned
#define ZPB push_back
#define ZPF push_front
#define PPB pop_back
#define PPF pop_front
#define next NXTNXT
#define UPB upper_bound
#define LWB lower_bound
#define mem(x,val) memset(x,val,sizeof(x))
using namespace std;
int n,a[710],b[710],c[710],dp[2][710][710],r[710][710],s[710][710],t[710][710],C[710][710];
const int mod=998244353;
void added(int &a,int b) {a+=b;if(a>=mod) a-=mod;return ;}
int mul(int a,int b) {return ((LL)a*b)%mod;}
int add(int a,int b) {a+=b;if(a>=mod) a-=mod;return a;}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i) cin>>b[i];
	for(int i=0;i<n;++i) cin>>c[i]; 
	for(int i=0;i<=n;++i){
		C[i][0]=1;
		for(int j=1;j<=i;++j) C[i][j]=add(C[i-1][j],C[i-1][j-1]);
	}
	dp[0][0][0]=dp[1][0][1]=1,r[0][0]=a[1],s[0][0]=b[1];
	for(int i=1;i<=n;++i){ 
		int now=i&1,nxt=now^1;
		mem(dp[nxt],0);
		for(int x=0;x<i;++x)
			for(int j=1;j<=i;++j)
				added(r[i][x],mul(dp[now][x][j],a[j+1])),
				added(s[i][x],mul(dp[now][i-x-1][j],b[j+1]));
		for(int j=0;j<i;++j)
			for(int k=1;k<=i;++k){
				added(dp[nxt][j][k],mul(dp[now][j][k],j+1)),
				added(dp[nxt][j+1][k+1],dp[now][j][k]),
				added(dp[nxt][j+1][k],mul(dp[now][j][k],i-j-1));
			}
	}
	for(int p=0;p<=n;++p){
		for(int y=0;y<=n;++y)
			for(int x=0;x<=p;++x) added(t[p][y],mul(c[x+y+(p>0)],r[p][x]));
	}
//	for(int i=0;i<=n;++i)
//		for(int j=0;j<=n;++j)
//				cout<<"r["<<i<<"]["<<j<<"]="<<r[i][j]<<"\n",
//				cout<<"s["<<i<<"]["<<j<<"]="<<s[i][j]<<"\n",
//				cout<<"t["<<i<<"]["<<j<<"]="<<t[i][j]<<"\n";
	for(int i=1;i<=n;++i){
		int ans=0;
		for(int p=1;p<=i;++p){
			int res=0;
			for(int y=0;y<=i-p;++y) added(res,mul(s[i-p][y],t[p-1][y]))/*,cerr<<"s="<<s[i-p][y]<<" t="<<t[p-1][y]<<"\n"*/; 
			added(ans,mul(res,C[i-1][p-1]));
		}
		cout<<ans<<" ";
	}
	cout<<"\n"; 
	return 0;
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...