比较学习-BZOJ3450: Easy 题解 和 BZOJ4318: OSU! 题解

本文探讨了使用动态规划解决期望问题的方法,并对比分析了两道题目:easy题求平方期望,OSU题求立方期望。文章详细解释了为什么直接用长度期望计算长度平方或立方期望会导致错误,并给出了正确的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这两道题长得几乎一模一样,只不过easy求的是平方,OSU求的是立方
于是决定把这两题一起切掉,没想到这其中大有玄机,还是我naive啊…


我们先看easy这题
考虑期望dp;dp[i]表示考虑到第i位的时候以i结尾的连续一段的期望长度
那么如果这位是o,dp[i]=dp[i-1]+1
如果这位是x,dp[i]=0
如果这位是?,dp[i]=(dp[i-1]+1)*0.5
根据期望的线性性,考虑在维护期望长度的同时计算每一位贡献
当这位是o的时候,长度从dp[i-1]变到dp[i-1]+1,则这段比原来多贡献了(dp[i1]+1)2dp[i1]2(dp[i−1]+1)2−dp[i−1]2
当这位是x的时候,没有贡献
当这位是?的时候,有1212的概率长度变为dp[i-1]+1,这时的贡献是0.5((dp[i1]+1)2dp[i1]2)0.5∗((dp[i−1]+1)2−dp[i−1]2),有1212的概率没有贡献
这样这题就做完了

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;
const double pi=acos(-1);

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

int n;
char s[300048];
double ans,dp[300048];

int main ()
{
    int i;
    n=getint();scanf("%s",s+1);
    dp[0]=0;ans=0;
    for (i=1;i<=n;i++)
        if (s[i]=='o')
        {
            dp[i]=dp[i-1]+1;
            ans+=2*dp[i-1]+1;
        }
        else if (s[i]=='?')
        {
            dp[i]=(dp[i-1]+1)*0.5;
            ans+=dp[i-1]+0.5;
        }
        else
            dp[i]=0;
    printf("%.4lf\n",ans);
    return 0;
}

然后osu那题,我想以此类推的做
osu的这题相当于全是?,算期望长度的那部分是一样的,算期望贡献的那部分,有p[i]的概率长度+1,此时贡献是p[i]((dp[i1]+1)3dp[i1]3)p[i]∗((dp[i−1]+1)3−dp[i−1]3),有1-p[i]的概率长度变为0,此时没有贡献
然后这道题就做完了


然后就WA了,我感到不可理解
上网查了资料,我发现了这样一句话
长度期望的平方不等于长度平方的期望,即E(x2)E(x)2E(x2)≠E(x)2
网上还说这个东西和方差有些关系,我就大力推了一波公式
联想方差的定义
设有nn个数x1,x2,x3...xn
定义算术平均数M=1nni=1xiM=1n∑i=1nxi
则方差D=1nni=1(xiM)2D=1n∑i=1n(xi−M)2
我们尝试把式子展开

D=1ni=1n(xiM)2=1ni=1n(x2i2xiM+M2)=1n(i=1nx2i2Mi=1nxi+nM2)=1ni=1nx2i2M1ni=1nxi+M2=1ni=1nx2i2M2+M2=1ni=1nx2iM2D=1n∑i=1n(xi−M)2=1n∑i=1n(xi2−2xiM+M2)=1n(∑i=1nxi2−2M∑i=1nxi+nM2)=1n∑i=1nxi2−2M1n∑i=1nxi+M2=1n∑i=1nxi2−2M2+M2=1n∑i=1nxi2−M2

我们把MM的定义式带回去
D=1ni=1nxi2(1ni=1nxi)2

用人话说,就是平方和的平均数与平均数的平方的差
然后我们发现,这个东西和期望是完全等价的,我们联想离散意义下期望的定义
E(x)=pixiE(x)=∑pixi

如果我们把上面的x1,x2...xnx1,x2...xn看做若干种情况下的收益,那么左边就是平方的期望,右边就是期望的平方,所以这两个是不等的,而且它们的差就是方差
(注意上面的给的方差的定义相当于默认所有情况出现的概率都是1n1n,如果用加权平均数算方差就和一般的期望无异)
于是我们有了一个很牛逼的方差的定义:D=平方的期望-期望的平方

我们来考虑为什么easy套用那个方法对了,而osu就错了
我们的dp[i]维护的都是长度的期望,是一次的
在easy中,(x+1)2x2=2x+1(x+1)2−x2=2x+1,我们发现他们的差实质上是一次的,所以可以直接用dp[i]相关的数据计算
在osu中,(x+1)3x3=3x2+3x+1(x+1)3−x3=3x2+3x+1,这是一个二次式,我们拿三次方直接减相当于用长度的期望的平方去计算了长度平方的期望,所以结果一定会比答案小,事实上也是这样的
所以osu的正确的操作应该是用dp[i]维护长度的期望,dp2[i]维护长度的平方的期望,dp[i]的转移和上面一样,dp2[i]可以用dp2[i-1]和dp[i-1]来转移

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;
const double pi=acos(-1);

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

int n;double p[100048];
double ans,dp[100048],dp2[100048];

inline double tri(double x) {return x*x*x;}

int main ()
{
    int i;
    n=getint();for (i=1;i<=n;i++) scanf("%lf",&p[i]);
    dp[0]=0;dp2[0]=0;ans=0;
    for (i=1;i<=n;i++)
    {
        dp[i]=(dp[i-1]+1)*p[i];
        dp2[i]=(dp2[i-1]+2*dp[i-1]+1)*p[i];
        ans+=(3*dp2[i-1]+3*dp[i-1]+1)*p[i];
    }
    printf("%.1lf\n",ans);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值