[NOIp 2016]换教室

Description

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

在可以选择的课程中,有 $2n$ 节课程安排在 $n$ 个时间段上。在第 $i$($1 \leq i \leq n$)个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 $c_i$ 上课,而另一节课程在教室 $d_i$ 进行。

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

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

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

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

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

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

Input

从标准输入读入数据。

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

第二行 $n$ 个正整数,第 $i$($1 \leq i \leq n$)个正整数表示 $c_i$,即第 $i$ 个时间段牛牛被安排上课的教室;保证 $1 \le c_i \le v$。

第三行 $n$ 个正整数,第 $i$($1 \leq i \leq n$)个正整数表示 $d_i$,即第 $i$ 个时间段另一间上同样课程的教室;保证 $1 \le d_i \le v$。

第四行 $n$ 个实数,第 $i$($1 \leq i \leq n$)个实数表示 $k_i$,即牛牛申请在第 $i$ 个时间段更换教室获得通过的概率。保证 $0 \le k_i \le 1$。

接下来 $e$ 行,每行三个正整数 $a_j, b_j, w_j$,表示有一条双向道路连接教室 $a_j, b_j$,通过这条道路需要耗费的体力值是 $w_j$;保证 $1 \le a_j, b_j \le v$, $1 \le w_j \le 100$。

保证 $1 \leq n \leq 2000$,$0 \leq m \leq 2000$,$1 \leq v \leq 300$,$0 \leq e \leq 90000$。

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

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

Output

输出到标准输出。

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

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

Sample Input

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

Sample Output

2.80

Sample Explanation

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

申请更换教室的时间段 申请通过的时间段 出现的概率 耗费的体力值 耗费的体力值的期望
1.0 8 8.0
1 1 0.8 4 4.8
0.2 8
2 2 0.2 0 6.4
0.8 8
3 3 0.5 4 6.0
0.5 8
1、2 1、2 0.16 4 4.48
1 0.64 4
2 0.04 0
0.16 8
1、3 1、3 0.4 0 2.8
1 0.4 4
3 0.1 4
0.1 8
2、3 2、3 0.1 4 5.2
2 0.1 0
3 0.4 4
0.4 8

HINT

测试点 $n$ $m$ $v$ 特殊性质1 特殊性质2
1 $\leq1$ $\leq1$ $\leq300$ × ×
2 $\leq2$ $\leq0$ $\leq20$
3 $\leq1$ $\leq100$
4 $\leq2$ $\leq300$
5 $\leq3$ $\leq0$ $\leq20$
6 $\leq1$ $\leq100$ ×
7 $\leq2$ $\leq300$ ×
8 $\leq10$ $\leq0$
9 $\leq1$ $\leq20$ ×
10 $\leq2$ $\leq100$ ×
11 $\leq10$ $\leq300$
12 $\leq20$ $\leq0$ $\leq20$ ×
13 $\leq1$ $\leq100$ ×
14 $\leq2$ $\leq300$
15 $\leq20$ ×
16 $\leq300$ $\leq0$ $\leq20$ ×
17 $\leq1$ $\leq100$
18 $\leq2$ $\leq300$
19 $\leq300$ ×
20 $\leq2000$ $\leq0$ $\leq20$ ×
21 $\leq1$
22 $\leq2$ $\leq100$
23 $\leq2000$
24 $\leq300$
25

特殊性质1:图上任意两点 $a_i, b_i$ ($a_i \neq b_i$)间,存在一条耗费体力最少的路径只包含一条道路。

特殊性质2:对于所有的 $1 \leq i \leq n, k_i = 1$。

时间限制:$1\texttt{s}$

空间限制:$512\texttt{MB}$

题解

1、根据期望的线性特点可以知道总路程的期望等于每相邻两节课之间路程的期望相加,一条路径期望只和前后两次选课有关,所以我们可以$DP$来做;

2、预处理出任两点间的最短路;

3、设$f[i][j][k]$为前$i$节课用了$j$次换课机会,$k$取值$true$和$false$,代表第$i$节课有没有申请换课,$f$值是满足这些限制条件下的最大期望;

4、转移就直接枚举下一节课申不申请换课,根据前后这两节课是否申请,统计出不同的前后上课地点的概率,算出期望。

5、转移方程:

[NOIp 2016]换教室

[NOIp 2016]换教室

 //It is made by Awson on 2017.10.22
#include <set>
#include <map>
#include <cmath>
#include <ctime>
#include <stack>
#include <queue>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define Min(a, b) ((a) < (b) ? (a) : (b))
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Abs(x) ((x) < 0 ? (-(x)) : (x))
using namespace std;
const int N = ;
const int V = ;
const int INF = ~0u>>; int n, m, v, e, a, b, w;
int c[N+], d[N+];
int mp[V+][V+];
double k[N+];
double f[N+][N+][]; void floyd() {
for (int k = ; k <= v; k++)
for (int i = ; i <= v; i++) if (k != i)
for (int j = ; j <= v; j++) if (i != j && k != j)
mp[i][j] = Min(mp[i][j], mp[i][k]+mp[k][j]);
}
void work() {
scanf("%d%d%d%d", &n, &m, &v, &e);
for (int i = ; i <= n; i++) for (int j = ; j <= m; j++) f[i][j][] = f[i][j][] = INF;
for (int i = ; i <= n; i++) scanf("%d", &c[i]);
for (int i = ; i <= n; i++) scanf("%d", &d[i]);
for (int i = ; i <= n; i++) scanf("%lf", &k[i]);
memset(mp, /, sizeof(mp));
for (int i = ; i <= v; i++) mp[][i] = mp[i][i] = ;
for (int i = ; i <= e; i++) {
scanf("%d%d%d", &a, &b, &w);
mp[a][b] = mp[b][a] = Min(mp[a][b], w);
}
floyd();
f[][][] = ;
for (int i = ; i <= n; i++)
for (int j = ; j <= Min(m, i-); j++) {
f[i][j][] = min(f[i-][j][]+mp[c[i-]][c[i]], f[i-][j][]+k[i-]*mp[d[i-]][c[i]]+(-k[i-])*mp[c[i-]][c[i]]);
f[i][j+][] = min(f[i-][j][]+k[i]*mp[c[i-]][d[i]]+(-k[i])*mp[c[i-]][c[i]], f[i-][j][]+k[i]*k[i-]*mp[d[i-]][d[i]]+k[i]*(-k[i-])*mp[c[i-]][d[i]]+(-k[i])*k[i-]*mp[d[i-]][c[i]]+(-k[i-])*(-k[i])*mp[c[i-]][c[i]]);
}
double ans = INF;
for (int i = ; i <= m; i++) ans = min(ans, min(f[n][i][], f[n][i][]));
printf("%.2lf\n", ans);
}
int main() {
work();
return ;
}
上一篇:php基础知识(1)


下一篇:URL Schemes