CF1810G The Maximum Prefix 题解
题目描述
对于一个长度 \(\le n\) 的数组 \(a\) , \(a_i\) 有 \(p_i\) 概率为 \(1\) ,有 \(1-p_i\) 概率为 \(-1\) 。
定义 \(s_i=\sum_{j=1}^ia_j\) , \(a\) 数组的最大前缀和为 \(S=\max_{i=0}^k s_i\) 。
给定 \(h_0,\cdots,h_n\) ,定义数组 \(a\) 的分数为 \(h_S\) 。
对 \(\forall 1\le k\le n\) ,求序列长度为 \(k\) 时,数组 \(a\) 的期望分数,对 \(10^9+7\) 取模。
数据范围
- \(1\le n\le 5\cdot 10^3,0\le h_i\lt 10^9+7\) 。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{256MB}\) 。
分析
如果按照常规方法 \(\texttt{dp}\) ,我们需要记录当前前缀和与最大前缀和两个状态,时间复杂度 \(\mathcal O(n^3)\) 。
假设最大前缀和为 \(t\) ,原本的前缀和可以看成一条从 \((0,0)\) 开始,走到 \((k,?)\) 的折线,碰到的最大纵坐标为 \(t\) 。
将折线沿 \(y=\frac t2\) 翻转,那么会变成从 \((0,t)\) 出发,走到 \((k,?)\) ,要求碰到的最小纵坐标为 \(0\) 。
但是如果枚举 \(k\) 从右往左 \(\texttt{dp}\) ,时间复杂度仍然是 \(\mathcal O(n^3)\) 。
注意到我们不需要对每个 \(S\) 求出前缀和为 \(S\) 的概率,而只需要计算对 \(h_S\) 加权后的概率,所以我们可以在初始值上动手脚。
具体的,考虑从左往右 \(\texttt{dp}\) ,令 \(f_{i,j,0/1}\) 表示走到 \((i,j)\) ,是否已经碰到过 \(x=0\) 的加权贡献和。
初始值 \(f_{0,S,[S=0]}=h_S\) ,转移乘上相应概率,在 \((k,?)\) 处统计答案。
时间复杂度 \(\mathcal O(n^2)\) 。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5005,mod=1e9+7;
int n,t;
int p[maxn],q[maxn];
int f[maxn][maxn][2];
inline int qpow(int a,int k)
{
int res=1;
for(;k;a=1ll*a*a%mod,k>>=1) if(k&1) res=1ll*res*a%mod;
return res;
}
inline void add(int &x,int y)
{
if((x+=y)>=mod) x-=mod;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1,x=0,y=0;i<=n;i++)
{
scanf("%d%d",&x,&y);
p[i]=1ll*x*qpow(y,mod-2)%mod,q[i]=mod+1-p[i];
}
for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) f[i][j][0]=f[i][j][1]=0;
for(int i=0;i<=n;i++) scanf("%d",&f[0][i][i==0]);
for(int i=1;i<=n;i++)
{
for(int j=0;j<=n;j++)
for(int k=0;k<=1;k++)
{
if(j!=0) add(f[i][j-1][k|(j-1==0)],1ll*f[i-1][j][k]*p[i]%mod);
if(j!=n) add(f[i][j+1][k|(j+1==0)],1ll*f[i-1][j][k]*q[i]%mod);
}
int res=0;
for(int j=0;j<=n;j++) add(res,f[i][j][1]);
printf("%d ",res);
}
putchar('\n');
}
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/17491971.html
浙公网安备 33010602011771号