BZOJ4720【NOIp2016】换教室 <概率DP>

Problem

【NOIp2016】换教室

题目描述

对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程。
在可以选择的课程中,有 2n2n 节课程安排在 nn 个时间段上。在第 ii1in1\le i\le n)个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 cic_i 上课,而另一节课程在教室 did_i 进行。
在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的 nn 节安排好的课程。如果学生想更换第 ii 节课程的教室,则需要提出申请。若申请通过,学生就可以在第 ii 个时间段去教室 did_i 上课,否则仍然在教室 cic_i 上课。
由于更换教室的需求太多,申请不一定能获得通过。通过计算,牛牛发现申请更换第 ii 节课程的教室时,申请被通过的概率是一个已知的实数 kik_i ,并且对于不同课程的申请,被通过的概率是互相独立的。
学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多 mm 节课程进行申请。这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请;牛牛可以申请自己最希望更换教室的 mm 门课程,也可以不用完这 mm 个申请的机会,甚至可以一门课程都不申请。
因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从一间教室赶到另一间教室。
牛牛所在的大学有 vv 个教室,有 ee 条道路。每条道路连接两间教室,并且是可以双向通行的。由于道路的长度和拥堵程度不同,通过不同的道路耗费的体力可能会有所不同。 当第 ii1in11\le i\le n-1)节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室。
现在牛牛想知道,申请哪几门课程可以使他因在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。

输入输出格式

输入格式
第一行四个整数 n,m,v,en,m,v,enn 表示这个学期内的时间段的数量;mm 表示牛牛最多可以申请更换多少节课程的教室;vv 表示牛牛学校里教室的数量;ee表示牛牛的学校里道路的数量。
第二行 nn 个正整数,第 i1ini(1\le i\le n)个正整数表示 cic_i ,即第 ii 个时间段牛牛被安排上课的教室;保证 1civ1\le c_i \le v
第三行 nn 个正整数,第 i1ini(1\le i\le n)个正整数表示 did_i ,即第 ii 个时间段另一间上同样课程的教室;保证 1div1\le d_i\le v
第四行 nn 个实数,第 i1ini(1\le i\le n)个实数表示 kik_i ,即牛牛申请在第 ii 个时间段更换教室获得通过的概率。保证 0ki10\le k_i \le 1
接下来 ee 行,每行三个正整数 aja_j, bjb_j,wjw_j,表示有一条双向道路连接教室 aja_j, bjb_j,通过这条道路需要耗费的体力值是 wjw_j;保证 1aj,bjv1\le a_j, b_j \le v1wj1001\le w_j\le 100
保证1n20001\le n\le 20000m20000\le m\le 20001v3001\le v\le 3000e900000\le e\le 90000
保证通过学校里的道路,从任何一间教室出发,都能到达其他所有的教室。
保证输入的实数最多包含 33 位小数。
输出格式
输出一行,包含一个实数,四舍五入精确到小数点后恰好22位,表示答案。你的输出必须和标准输出完全一样才算正确。
测试数据保证四舍五入后的答案和准确答案的差的绝对值不大于4×1034\times 10^{-3}。(如果你不知道什么是浮点误差,这段话可以理解为:对于大多数的算法,你可以正常地使用浮点数类型而不用对它进行特殊的处理)

输入输出样例

输入样例#1

1
2
3
4
5
6
7
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

1
2.80

提示

  • 道路中可能会有多条双向道路连接相同的两间教室。 也有可能有道路两端连接的是同一间教室。
  • 请注意区分n,m,v,en,m,v,e的意义, nn不是教室的数量, mm不是道路的数量。

标签:概率DP

Solution

期望题,有点超纲。
考虑DP\mathrm{DP}f[i][j][0/1]f[i][j][0/1]表示选到第ii个时间段,换了jj节课,这节课是否换的期望。
那么可以枚举是否换成功的情况进行转移。具体见代码。
DP\mathrm{DP}方程挺长,但还是比较好想的。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <cstdio>
#define MAX_V 300
#define MAX_N 2000
#define INF 0x3f3f3f3f
using namespace std;
typedef double dnt;
int n, m, v, e;
int c[MAX_N+5], d[MAX_N+5]; dnt k[MAX_N+5];
int dis[MAX_V+5][MAX_V+5]; dnt f[MAX_N+5][MAX_N+5][2];
void Floyed() {
for (int i = 1; i <= v; i++) for (int j = 1; j <= v; j++) dis[i][j] = i == j ? 0 : INF;
for (int i = 0, x, y, z; i < e; i++) scanf("%d%d%d", &x, &y, &z), dis[x][y] = dis[y][x] = min(dis[x][y], z);
for (int k = 1; k <= v; k++) for (int i = 1; i <= v; i++)
for (int j = 1; j <= v; j++) dis[i][j] = min(dis[i][j], dis[i][k]+dis[k][j]);
}
void DP() {
for (int i = 1; i <= n; i++) for (int j = 0; j <= m; j++) for (int k = 0; k <= 1; k++) f[i][j][k] = INF;
f[1][0][0] = f[1][1][1] = 0;
for (int i = 2; i <= n; i++) for (int j = 0; j <= min(m, i); j++) {
f[i][j][0] = min(f[i-1][j][0]+dis[c[i-1]][c[i]], f[i-1][j][1]+dis[d[i-1]][c[i]]*k[i-1]+dis[c[i-1]][c[i]]*(1-k[i-1]));
if (j) f[i][j][1] = min(f[i-1][j-1][0]+dis[c[i-1]][d[i]]*k[i]+dis[c[i-1]][c[i]]*(1-k[i]), f[i-1][j-1][1]+dis[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i])+dis[c[i-1]][d[i]]*(1-k[i-1])*k[i]+dis[d[i-1]][c[i]]*k[i-1]*(1-k[i])+dis[d[i-1]][d[i]]*k[i-1]*k[i]);
}
}
dnt MIN() {dnt ret = INF; for (int j = 0; j <= m; j++) for (int k = 0; k < 2; k++) ret = min(ret, f[n][j][k]); return ret;}
int main() {
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", k+i);
Floyed(), DP(), printf("%.2lf", MIN()); return 0;
}