【BZOJ1076】【SCOI2008】奖励关&【BZOJ4318】OSU!()期望dp&【洛谷1850】换教室

Description

  你正在玩你最喜欢的电子游戏,并且刚刚进入一个奖励关。在这个奖励关里,系统将依次随机抛出k次宝物,每次你都可以选择吃或者不吃(必须在抛出下一个宝物之前做出选择,且现在决定不吃的宝物以后也不能再吃)。
宝物一共有n种,系统每次抛出这n种宝物的概率都相同且相互独立。也就是说,即使前k-1次系统都抛出宝物1,这种情况是有可能出现的,尽管概率非常小),第k次抛出各个宝物的概率依然均为1/n。 获取第i种宝物将得到Pi分,但并不是每种宝物都是可以随意获取的。第i种宝物有一个前提宝物集合Si。只有当Si中所有宝物都至少吃过一次,才能吃第i种宝物(如果系统抛出了一个目前不能吃的宝物,相当于白白的损失了一次机会)。注意,Pi可以是负数,但如果它是很多高分宝物的前提,损失短期利益而吃掉这个负分宝物将获得更大的长期利益。 假设你采取最优策略,平均情况你一共能在奖励关得到多少分值?

Input

  第一行为两个正整数k和n,即宝物的数量和种类。以下n行分别描述一种宝物,其中第一个整数代表分值,随
后的整数依次代表该宝物的各个前提宝物(各宝物编号为1到n),以0结尾。

Output

  输出一个实数,保留六位小数,即在最优策略下平均情况的得分。

Sample Input

1 2
1 0
2 0

Sample Output

1.500000

HINT

【数据规模】

1<=k<=100,1<=n<=15,分值为[-10^6,10^6]内的整数。

题解

总结:先说一下期望dp吧。其实期望dp和普通dp的思想方法相同,只不过算贡献的时候要乘上贡献的期望,算期望的时候用到的其他变量也要考虑他们的期望。

拿具体的题说吧。

至于吃还是不吃 这是个问题,对于这种最优策略的期望DP 我们一般都是从后往前推,枚举每次挑战 枚举此时的状态 枚举宝物是哪种。如果当前的宝物可以吃 就在吃与不吃的后继状态中选择最大值加到当前状态上,如果当前的宝物不能吃 只能选择不吃的后继状态加到当前状态上,最后输出f[1][0]就是答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
double dp[110][1<<16],w[20];
int st[20];
int main(){
	int n,K,x;
	scanf("%d%d",&K,&n);
	for(int i=1;i<=n;i++){
		scanf("%lf",&w[i]);
		while(scanf("%d",&x)==1&&x) st[i]|=(1<<(x-1));
	}
	int S=(1<<n);
	for(int i=K;i>=1;i--){
		for(int j=0;j<S;j++){
			for(int k=1;k<=n;k++){
				if((j|st[k])==j) dp[i][j]+=max(dp[i+1][j],dp[i+1][j|(1<<(k-1))]+w[k])/n;
				else dp[i][j]+=dp[i+1][j]/n;
			}
			
		}
	}
	printf("%.6lf\n",dp[1][0]);
	return 0;
}

第二题

Description

osu 是一款群众喜闻乐见的休闲软件。 我们可以把osu的规则简化与改编成以下的样子: 一共有n次操作,每次操作只有成功与失败之分,成功对应1,失败对应0,n次操作对应为1个长度为n的01串。在这个串中连续的 X个1可以贡献X^3 的分数,这x个1不能被其他连续的1所包含(也就是极长的一串1,具体见样例解释) 现在给出n,以及每个操作的成功率,请你输出期望分数,输出四舍五入后保留1位小数。 

Input

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

Output

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

Sample Input

3
0.5
0.5
0.5

Sample Output

6.0

HINT

【样例说明】 

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

N<=100000

题解

假如这个01串使确定的,考虑每新增一个位置,如果这个位置是 0 ,则贡献为 0 ,否则贡献为 (x+1)3x3=3x2+3x+1 ,其中 x 为加入之前最长的全1后缀的长度 现在这个问题变成了期望问题,那么我们只需要维护一个 x 的期望和 x2 的期望即可。注意平方的期望不等于期望的平方。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100010;
double f[maxn],p[maxn],l1[maxn],l2[maxn];
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lf",&p[i]);
		l1[i]=(l1[i-1]+1)*p[i];
		l2[i]=(l2[i-1]+2*l1[i-1]+1)*p[i];
		f[i]=f[i-1]+(3*l2[i-1]+3*l1[i-1]+1)*p[i];
	}
	printf("%.1lf\n",f[n]);
	
	return 0;
}

第三题

题目描述

对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程。

在可以选择的课程中,有 2n2n2n 节课程安排在 nnn 个时间段上。在第 iii1≤i≤n1 \leq i \leq n1in)个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 cic_ici 上课,而另一节课程在教室 did_idi 进行。

在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的 nnn 节安排好的课程。如果学生想更换第 iii 节课程的教室,则需要提出申请。若申请通过,学生就可以在第 iii 个时间段去教室 did_idi 上课,否则仍然在教室 cic_ici 上课。

由于更换教室的需求太多,申请不一定能获得通过。通过计算,牛牛发现申请更换第 iii 节课程的教室时,申请被通过的概率是一个已知的实数 kik_iki,并且对于不同课程的申请,被通过的概率是互相独立的。

学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多 mmm 节课程进行申请。这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请;牛牛可以申请自己最希望更换教室的 mmm 门课程,也可以不用完这 mmm 个申请的机会,甚至可以一门课程都不申请。

因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从一间教室赶到另一间教室。

牛牛所在的大学有 vvv 个教室,有 eee 条道路。每条道路连接两间教室,并且是可以双向通行的。由于道路的长度和拥堵程度不同,通过不同的道路耗费的体力可能会有所不同。 当第 iii1≤i≤n−11 \leq i \leq n-11in1)节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室。

现在牛牛想知道,申请哪几门课程可以使他因在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。

输入输出格式

输入格式:

第一行四个整数 n,m,v,en,m,v,en,m,v,ennn 表示这个学期内的时间段的数量;mmm 表示牛牛最多可以申请更换多少节课程的教室;vvv 表示牛牛学校里教室的数量;eee表示牛牛的学校里道路的数量。

第二行 nnn 个正整数,第 iii1≤i≤n1 \leq i \leq n1in)个正整数表示 cic_ici,即第 iii 个时间段牛牛被安排上课的教室;保证 1≤ci≤v1 \le c_i \le v1civ

第三行 nnn 个正整数,第 iii1≤i≤n1 \leq i \leq n1in)个正整数表示 did_idi,即第 iii 个时间段另一间上同样课程的教室;保证 1≤di≤v1 \le d_i \le v1div

第四行 nnn 个实数,第 iii1≤i≤n1 \leq i \leq n1in)个实数表示 kik_iki,即牛牛申请在第 iii 个时间段更换教室获得通过的概率。保证 0≤ki≤10 \le k_i \le 10ki1

接下来 eee 行,每行三个正整数 aj,bj,wja_j, b_j, w_jaj,bj,wj,表示有一条双向道路连接教室 aj,bja_j, b_jaj,bj,通过这条道路需要耗费的体力值是 wjw_jwj;保证 1≤aj,bj≤v1 \le a_j, b_j \le v1aj,bjv1≤wj≤1001 \le w_j \le 1001wj100

保证 1≤n≤20001 \leq n \leq 20001n20000≤m≤20000 \leq m \leq 20000m20001≤v≤3001 \leq v \leq 3001v3000≤e≤900000 \leq e \leq 900000e90000

保证通过学校里的道路,从任何一间教室出发,都能到达其他所有的教室。

保证输入的实数最多包含 333 位小数。

输出格式:

输出一行,包含一个实数,四舍五入精确到小数点后恰好222位,表示答案。你的输出必须和标准输出完全一样才算正确。

测试数据保证四舍五入后的答案和准确答案的差的绝对值不大于 4×10−34 \times 10^{-3}4×103。 (如果你不知道什么是浮点误差,这段话可以理解为:对于大多数的算法,你可以正常地使用浮点数类型而不用对它进行特殊的处理)

输入输出样例

输入样例#1:
3 2 3 3
2 1 2
1 2 1
0.8 0.2 0.5 
1 2 5
1 3 3
2 3 1
输出样例#1:
2.80

说明

【样例1说明】

所有可行的申请方案和期望收益如下表:

【提示】

  1. 道路中可能会有多条双向道路连接相同的两间教室。 也有可能有道路两端连接

的是同一间教室。

2.请注意区分n,m,v,e的意义, n不是教室的数量, m不是道路的数量。

特殊性质1:图上任意两点 aia_iai, bib_ibi, aia_iaibib_ibi间,存在一条耗费体力最少的路径只包含一条道路。

特殊性质2:对于所有的 $1≤ i≤ n$, ki=1k_i= 1ki=1

题解

Floyed+dp。先把两两点距算出来,然后考虑一个Dp,我们类似一个背包地设,dp[i][j][0]表示前i天,换j次,并且第i天不换的最小期望距离。那么dp[i][j][1]就表示前i天换j次,且第i天换的最小期望距离。

再设cc表示从c[i-1]到c[i]的距离,那么cd,dd,dc类似。

那么考虑一下转移:

dp[i][j][0]=min(dp[i-1][j][0]+cc,dp[i-1][j-1][1]+p[i-1]*dc+(1-p[i-1])*cc)这个期望一定要两个都要考虑啊。

dp[i][j][1]=min(dp[i-1][j][0]+p[i]*cd+(1-p[i])*cc,dp[i-1][j][1]+p[i-1]*p[i]*dd+(1-p[i-1])*p[i]*cd+p[i-1]*(1-p[i])*dc+(1-p[i-1])*(1-p[i])*dd);

从这里我们也可以看到一点期望类转移方程的套路,就是这一步不仅要考虑当前的选择概率,同时也要考虑前一次选择的概率,应该不会有两种以上的选择。那么这道题就差不多了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2010; 
int n,m,V,E;
int c[maxn],d[maxn];
double p[maxn],dp[3][maxn][3];
int g[maxn][maxn];
int main(){
    int x,y,z;
    scanf("%d%d%d%d",&n,&m,&V,&E);
    for(int i=1;i<=n;i++) scanf("%d",&c[i]);
    for(int i=1;i<=n;i++) scanf("%d",&d[i]);
    for(int i=1;i<=n;i++) scanf("%lf",&p[i]);
    
    memset(g,127/3,sizeof(g));
    int inf=g[0][0];
    for(int i=1;i<=E;i++){
        scanf("%d%d%d",&x,&y,&z);
        g[x][y]=min(g[x][y],z);
        g[y][x]=g[x][y];
    }
    
    for(int k=1;k<=V;k++) for(int i=1;i<=V;i++) for(int j=1;j<=V;j++) 
    if(g[i][k]!=inf&&g[k][j]!=inf) g[i][j]=min(g[i][k]+g[k][j],g[i][j]);
    
    for(int i=1;i<=V;i++) g[i][i]=0;
    
    for(int i=0;i<=1;i++) for(int j=0;j<=m;j++) dp[i][j][0]=dp[i][j][1]=inf;
    dp[1][0][0]=dp[1][1][1]=0;
    int now,pre;
    for(int i=2;i<=n;i++){
        now=i&1;pre=now^1;
        int nl=c[i-1],nr=d[i-1],nx=c[i],ny=d[i];
        double cc=(double)g[nl][nx],cd=(double)g[nl][ny],dc=(double)g[nr][nx],dd=(double)g[nr][ny];

        dp[now][0][0]=dp[pre][0][0]+cc; 
        for(int j=1;j<=m;j++){
            if(j>i) break;
            dp[now][j][0]=min(dp[pre][j][0]+cc,dp[pre][j][1]+p[i-1]*dc+(1.0-p[i-1])*cc);
            dp[now][j][1]=min(dp[pre][j-1][0]+p[i]*cd+(1-p[i])*cc,dp[pre][j-1][1]+p[i-1]*p[i]*dd+(1.0-p[i-1])*p[i]*cd+(1.0-p[i-1])*(1.0-p[i])*cc+p[i-1]*(1.0-p[i])*dc);
        }
    }
    double ans=inf;
    for(int i=0;i<=m;i++)
    ans=min(ans,min(dp[n&1][i][0],dp[n&1][i][1]));
    
    printf("%.2lf\n",ans);

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值