题解:CF1988F Heartbeat
猜测目标
提前声明:本篇文章比较偏向小白因为本作者是蒟蒻。
本题解参考官方做法。
一看到数据范围,容易想到我们需要
不过看看时限,还是
设计状态
首先,这一道题和全排列有关,容易想到肯定有一个
但是如果我们如果这么定义:
显然在没有得到任何性质之前,这根本转移不了啊。
发现问题是无法同时维护前缀最大值和后缀最大值的个数,于是我们只能含泪去掉一个,于是定义:
因为我们拆开了
比如当
因为如果你的
也就是说,或者根本统计不了,这个到时候再说吧,先放着。
转移方程
我们先考虑怎么转移。
首先,容易发现
具体来说,
为什么呢?考虑将
然后,将这个数组倒过来,这样前缀最大值就变成后缀最大值啦。
考虑上升点对,很明显原来的上升点对变成了下降点对,下降点对变成了上升点对。
但是因为总共只有
所以,我们只要把
怎么从
至于为什么不能直接插入
讨论:
- 将
1 插入到j 个上升点对之间。 - 将
1 插入到第一个位置。 - 将
1 插入到最后一个位置。 - 其他情况。
对于第一种情况,虽然
所以则有:
当然,在这个情况下,
对于第二种情况,
所以则有:
对于第三种情况,既不会产生新的上升点对,也不会产生新的前缀最大值。
所以则有:
对于第四种情况,则会产生一个新的上升点对,不会产生新的前缀最大值。
所以则有:
将所有情况讨论完后,总结一下,有如下转移:
对不能插入
- 首先,对于上升点对来说,插入
i+1 是可计算的。 - 但是,对于前缀最大值来说,如果在某个位置插入了
i+1 ,那么就会使得这个位置的后面,原先是前缀最大值的将不是前缀最大值,但是我们无法统计一个位置后面的前缀最大值个数,自然就不能插入i+1 啦。 - 就算方法改了之后还是可以插入
i+1 ,那我估计转移的复杂度应该不是\mathcal O(1) 啦,估计最后优化不到正解的程度,而且不如插入1 简单。 - 所以这里不讨论插入
i+1 的转移,而且我估计以当前状态应该是很难转移了。
至于空间复杂度,很明显,因为所有式子都是
统计答案
首先,如讲述“状态定义”标题时已经有提到过,我们要让
我们想想,前缀最大值和后缀最大值,怎样才能断开呢?
其实,都是最大值嘛,那我们就像前文对不能插入
所以,我们枚举全排列
于是,答案如下:
其中,
然后至于
为什么呢?因为我们其实要的只是相对顺序。
至于上升点对,如果
如果真就这样统计的话,总时间复杂度为
但是,我们观察到有一些式子的部分只与部分变量有关,于是转换,得:
将
于是,我们设:
这两个式子的预处理复杂度均为
带入原式,得:
此时时间复杂度被降为
发现此时只要预处理
为什么不能预处理
因为
所以,这里预处理的本质是什么?
就是通过变换
否则,如果你提不出去(就像
这样我们就从题目里面又获取到了一个知识啦。
那为什么不能预处理
因为你计算
于是,我们还是将上面
将
这样,我们就可以愉快的预处理
设:
如果不理解,可以将
当然,逆着推过来可以知道是否正确,那怎么顺着推过来呢?
令
为了让 所以数学还是很重要滴。
这样,我们将
这样,再预处理
再考虑一下初始值吧,考虑到
很明显,在没有数的时候,肯定啥贡献都没有啊。
至于
代码:
#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;
}