• P1654 OSU!


    P1654 OSU!

    题目

    题目背景

    原 《产品排序》 参见P2577

    题目描述

    osu 是一款群众喜闻乐见的休闲软件。

    我们可以把osu的规则简化与改编成以下的样子:

    一共有n次操作,每次操作只有成功与失败之分,成功对应1,失败对应0,n次操作对应为1个长度为n的01串。在这个串中连续的 (X)(1) 可以贡献 (X^3) 的分数,这x个1不能被其他连续的1所包含(也就是极长的一串1,具体见样例解释)

    现在给出n,以及每个操作的成功率,请你输出期望分数,输出四舍五入后保留1位小数。

    输入格式

    第一行有一个正整数n,表示操作个数。接下去n行每行有一个[0,1]之间的实数,表示每个操作的成功率。

    输出格式

    只有一个实数,表示答案。答案四舍五入后保留1位小数。

    输入输出样例

    输入 #1

    3 
    0.5 
    0.5 
    0.5
    

    输出 #1

    6.0
    

    说明/提示

    【样例说明】

    000分数为0,001分数为1,010分数为1,100分数为1,101分数为2,110分数为8,011分数为8,111分数为27,总和为48,期望为48/8=6.0

    N<=100000

    思路&代码

    我的做法可能比较复杂,但思路可能对你有所启发.

    暴力

    不难想到,设(f_i)表示前(i)个操作后的期望得分,则有:

    [f_i=sum_{j=1}^iBigg( Big(f_{j-1}+(i-j)^3Big)cdot Big(prod^i_{k=j+1}p_k Big)cdot Big(1-p_jBig) Bigg) ]

    可以做到(O(n^2)),80分.

    代码

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    const int N = 100010;
    int n;
    double p[N];
    double f[N];
    
    double pow3(double a) {
    	return a * a * a;
    }
    int main() {
    	scanf("%d" , &n);
    	for(int i = 1 ; i <= n ; i++)
    		scanf("%lf" , p + i);
    //	f[1] = 0 * (1 - p[1]) + 1 * p[1];
    	for(int i = 1 ; i <= n ; i++) {
    		double P = 1;
    		for(int j = i ; j >= 1 ; j--)
    			f[i] += (f[j - 1] + pow3(i - j)) * P * (1 - p[j]) , P *= p[j];
    		f[i] += pow3(i) * P;
    	}
    	printf("%.1lf" , f[n]);
    	return 0;
    } 
    

    优化

    我们可以考虑对上面的式子进行类似前缀和的优化,我们尝试将与(i)有关的项从(sum)中分离出来,便于预处理.

    我们先预处理一个(prod_i=prod_{j=1}^i p_j),这样,(prod_{j=l}^ra_j=prod_r div prod_{l-1}).

    (为了看懂下式,你需要知道(sum (acdot b_i)=acdot (sum b_i))((a)是一个与(i)无关的量)).

    为了简化式子,我们用(t)代替((1-p_j)div prod_j)(注意(k)(j)有关,应该放在(sum)内).

    [egin{aligned} f_i &= sum_{j=1}^iBigg( Big(f_{j-1}+(i-j)^3Big)cdot Big(frac{prod_i}{prod_j} Big)cdot Big(1-p_jBig) Bigg)\ f_i &= prod_iBigg( sum^i_{j=1}Big( f_{j-1} +(i-j)^3 Big)cdot t Bigg)\ f_i &= prod_iBigg( sum^i_{j=1}Big( f_{j-1} +i^3-3i^2j+3ij^2-j^3 Big)cdot t Bigg)\ f_i &= prod_iBigg( Big(sum^i_{j=1} f_{j-1}cdot tBig) +Big(sum^i_{j=1} i^3cdot t Big) -Big(sum^i_{j=1} 3i^2jcdot tBig) +Big(sum^i_{j=1} 3ij^2cdot tBig) -Big(sum^i_{j=1} j^3cdot tBig) Bigg)\ f_i &= prod_iBigg( Big(sum^i_{j=1} f_{j-1}cdot tBig) +i^3Big(sum^i_{j=1} t Big) -3i^2Big(sum^i_{j=1} jcdot tBig) +3iBig(sum^i_{j=1} j^2cdot tBig) -Big(sum^i_{j=1} j^3cdot tBig) Bigg)\ end{aligned} ]

    到这里,(sum)内的每一项都和(i)无关了,这就很快乐了.

    我们分别令:注意(b_0=1).

    [egin{aligned} a_i &=Big(sum^i_{j=1} f_{j-1}cdot frac{1-p_i}{prod_i} Big)\ b_i &=Big(sum^i_{j=1} frac{1-p_i}{prod_i} Big)\ c_i &=Big(sum^i_{j=1} jcdot frac{1-p_i}{prod_i}Big)\ d_i &=Big(sum^i_{j=1} j^2cdot frac{1-p_i}{prod_i}Big)\ e_i &=Big(sum^i_{j=1} j^3cdot frac{1-p_i}{prod_i}Big)\ end{aligned} ]

    则有:

    [f_i = a_i +i^3cdot b_i -3i^2cdot c_i +3icdot d_i -e_i ]

    这就清爽多了,可以在(O(n))内处理.

    为了提高精度,注意用long double.为了防止概率中出现0,我们以0为界,分开多块处理.由于块间互不影响,每一块内答案加和就是答案.具体可以看代码.

    代码

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    
    #define double long double
    const int N = 100010;
    int n;
    double f[N];
    double prod[N];
    
    double a[N] , b[N] , c[N] , d[N] , e[N];
    
    double pow3(double a) {
    	return a * a * a;
    }
    
    double solve(double* p , int n) {
    	prod[0] = 1;
    	for(int i = 1 ; i <= n ; i++)
    		prod[i] = prod[i - 1] * p[i];
    	
    	f[0] = 0;
    	b[0] = 1;
    	for(long long i = 1 ; i <= n ; i++) {
    		double t = (1 - p[i]) / prod[i];
    		a[i] = a[i - 1] + f[i - 1] * t;
    		b[i] = b[i - 1] + t;
    		c[i] = c[i - 1] + t * i;
    		d[i] = d[i - 1] + t * i * i;
    		e[i] = e[i - 1] + t * i * i * i;
    		
    		f[i] = prod[i] * (
    		           a[i]
    		           + b[i] * ((double)i * i * i)
    		           - c[i] * ((double)3 * i * i)
    		           + d[i] * ((double)3 * i)
    		           - e[i]
    		       );
    	}
    	return f[n];
    }
    
    double p[N];
    int main() {
    	scanf("%d" , &n);
    	for(int i = 1 ; i <= n ; i++){
    		scanf("%Lf" , p + i);
    	}
    	
    	double ans = 0;
    	int l = 1 , r = 1;
    	
    	while(l <= n) {
    		r = l;
    		while(r <= n && p[r] != 0)++r;
    		--r;
    		ans += solve(p + l - 1 , r - l + 1) ;
    		l = r + 2;
    	}
    	printf("%.1Lf" , ans);
    	return 0;
    }
    
  • 相关阅读:
    百度地图生成器添加标注不显示
    为web文件夹添加IIS应用程序池用户权限
    SSL证书 .pem转.pfx
    sql获取表的所有字段及属性
    sql server不用安装sql管理工具开启sa
    asp.net mvc+webuploader大文件分片上传
    将WinForm程序(含多个非托管Dll)合并成一个exe的方法
    手机访问网站自动跳转到手机版
    c++和cuda混合编程 实现传统神经网络
    leetcode 338
  • 原文地址:https://www.cnblogs.com/dream1024/p/15412941.html
Copyright © 2020-2023  润新知