NOIP2016 提高组 D1T3
对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程。
在可以选择的课程中,有 2 n 2n 2n 节课程安排在 n n n 个时间段上。在第 i i i( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n)个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 c i c_i ci 上课,而另一节课程在教室 d i d_i di 进行。
在不提交任何申请的情况下,学生们需要按时间段的顺序依次完成所有的 n n n 节安排好的课程。如果学生想更换第 i i i 节课程的教室,则需要提出申请。若申请通过,学生就可以在第 i i i 个时间段去教室 d i d_i di 上课,否则仍然在教室 c i c_i ci 上课。
由于更换教室的需求太多,申请不一定能获得通过。通过计算,牛牛发现申请更换第 i i i 节课程的教室时,申请被通过的概率是一个已知的实数 k i k_i ki,并且对于不同课程的申请,被通过的概率是互相独立的。
学校规定,所有的申请只能在学期开始前一次性提交,并且每个人只能选择至多 m m m 节课程进行申请。这意味着牛牛必须一次性决定是否申请更换每节课的教室,而不能根据某些课程的申请结果来决定其他课程是否申请;牛牛可以申请自己最希望更换教室的 m m m 门课程,也可以不用完这 m m m 个申请的机会,甚至可以一门课程都不申请。
因为不同的课程可能会被安排在不同的教室进行,所以牛牛需要利用课间时间从一间教室赶到另一间教室。
牛牛所在的大学有 v v v 个教室,有 e e e 条道路。每条道路连接两间教室,并且是可以双向通行的。由于道路的长度和拥堵程度不同,通过不同的道路耗费的体力可能会有所不同。 当第 i i i( 1 ≤ i ≤ n − 1 1 \leq i \leq n-1 1≤i≤n−1)节课结束后,牛牛就会从这节课的教室出发,选择一条耗费体力最少的路径前往下一节课的教室。
现在牛牛想知道,申请哪几门课程可以使他因在教室间移动耗费的体力值的总和的期望值最小,请你帮他求出这个最小值。
第一行四个整数 n , m , v , e n,m,v,e n,m,v,e。 n n n 表示这个学期内的时间段的数量; m m m 表示牛牛最多可以申请更换多少节课程的教室; v v v 表示牛牛学校里教室的数量; e e e 表示牛牛的学校里道路的数量。
第二行 n n n 个正整数,第 i i i( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n)个正整数表示 c i c_i ci,即第 i i i 个时间段牛牛被安排上课的教室;保证 1 ≤ c i ≤ v 1 \le c_i \le v 1≤ci≤v。
第三行 n n n 个正整数,第 i i i( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n)个正整数表示 d i d_i di,即第 i i i 个时间段另一间上同样课程的教室;保证 1 ≤ d i ≤ v 1 \le d_i \le v 1≤di≤v。
第四行 n n n 个实数,第 i i i( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n)个实数表示 k i k_i ki,即牛牛申请在第 i i i 个时间段更换教室获得通过的概率。保证 0 ≤ k i ≤ 1 0 \le k_i \le 1 0≤ki≤1。
接下来 e e e 行,每行三个正整数 a j , b j , w j a_j, b_j, w_j aj,bj,wj,表示有一条双向道路连接教室 a j , b j a_j, b_j aj,bj,通过这条道路需要耗费的体力值是 w j w_j wj;保证 1 ≤ a j , b j ≤ v 1 \le a_j, b_j \le v 1≤aj,bj≤v, 1 ≤ w j ≤ 100 1 \le w_j \le 100 1≤wj≤100。
保证 1 ≤ n ≤ 2000 1 \leq n \leq 2000 1≤n≤2000, 0 ≤ m ≤ 2000 0 \leq m \leq 2000 0≤m≤2000, 1 ≤ v ≤ 300 1 \leq v \leq 300 1≤v≤300, 0 ≤ e ≤ 90000 0 \leq e \leq 90000 0≤e≤90000。
保证通过学校里的道路,从任何一间教室出发,都能到达其他所有的教室。
保证输入的实数最多包含 3 3 3 位小数。
输出一行,包含一个实数,四舍五入精确到小数点后恰好 2 2 2 位,表示答案。你的输出必须和标准输出完全一样才算正确。
测试数据保证四舍五入后的答案和准确答案的差的绝对值不大于 4 × 1 0 − 3 4 \times 10^{-3} 4×10−3。(如果你不知道什么是浮点误差,这段话可以理解为:对于大多数的算法,你可以正常地使用浮点数类型而不用对它进行特殊的处理)
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
2.80
样例 1 说明
所有可行的申请方案和期望收益如下:
申请通过的时间段 | 出现的概率 | 耗费的体力值 |
---|---|---|
无 | 1.0 1.0 1.0 | 8 8 8 |
申请通过的时间段 | 出现的概率 | 耗费的体力值 |
---|---|---|
1 1 1 | 0.8 0.8 0.8 | 4 4 4 |
无 | 0.2 0.2 0.2 | 8 8 8 |
申请通过的时间段 | 出现的概率 | 耗费的体力值 |
---|---|---|
2 2 2 | 0.2 0.2 0.2 | 0 0 0 |
无 | 0.8 0.8 0.8 | 8 8 8 |
申请通过的时间段 | 出现的概率 | 耗费的体力值 |
---|---|---|
3 3 3 | 0.5 0.5 0.5 | 4 4 4 |
无 | 0.5 0.5 0.5 | 8 8 8 |
申请通过的时间段 | 出现的概率 | 耗费的体力值 |
---|---|---|
1 , 2 1,2 1,2 | 0.16 0.16 0.16 | 4 4 4 |
1 1 1 | 0.64 0.64 0.64 | 4 4 4 |
2 2 2 | 0.04 0.04 0.04 | 0 0 0 |
无 | 0.16 0.16 0.16 | 8 8 8 |
申请通过的时间段 | 出现的概率 | 耗费的体力值 |
---|---|---|
1 , 3 1,3 1,3 | 0.4 0.4 0.4 | 0 0 0 |
1 1 1 | 0.4 0.4 0.4 | 4 4 4 |
3 3 3 | 0.1 0.1 0.1 | 4 4 4 |
无 | 0.1 0.1 0.1 | 8 8 8 |
申请通过的时间段 | 出现的概率 | 耗费的体力值 |
---|---|---|
2 , 3 2,3 2,3 | 0.1 0.1 0.1 | 4 4 4 |
2 2 2 | 0.1 0.1 0.1 | 0 0 0 |
3 3 3 | 0.4 0.4 0.4 | 4 4 4 |
无 | 0.4 0.4 0.4 | 8 8 8 |
因此,最优方案为:申请更换第 1 , 3 1,3 1,3 个时间段的上课教室。耗费的体力值的期望为 2.8 2.8 2.8。
提示
数据范围与说明
测试点编号 | n ≤ n\le n≤ | m ≤ m\le m≤ | v ≤ v\le v≤ | 是否具有特殊性质 1 | 是否具有特殊性质 2 |
---|---|---|---|---|---|
1 | 1 1 1 | 1 1 1 | 300 300 300 | × \times × | × \times × |
2 | 2 2 2 | 0 0 0 | 20 20 20 | × \times × | × \times × |
3 | 2 2 2 | 1 1 1 | 100 100 100 | × \times × | × \times × |
4 | 2 2 2 | 2 2 2 | 300 300 300 | × \times × | × \times × |
5 | 3 3 3 | 0 0 0 | 20 20 20 | √ \surd √ | √ \surd √ |
6 | 3 3 3 | 1 1 1 | 100 100 100 | √ \surd √ | × \times × |
7 | 3 3 3 | 2 2 2 | 300 300 300 | × \times × | × \times × |
8 | 10 10 10 | 0 0 0 | 300 300 300 | √ \surd √ | √ \surd √ |
9 | 10 10 10 | 1 1 1 | 20 20 20 | √ \surd √ | × \times × |
10 | 10 10 10 | 2 2 2 | 100 100 100 | × \times × | × \times × |
11 | 10 10 10 | 10 10 10 | 300 300 300 | × \times × | √ \surd √ |
12 | 20 20 20 | 0 0 0 | 20 20 20 | √ \surd √ | × \times × |
13 | 20 20 20 | 1 1 1 | 100 100 100 | × \times × | × \times × |
14 | 20 20 20 | 2 2 2 | 300 300 300 | √ \surd √ | × \times × |
15 | 20 20 20 | 20 20 20 | 300 300 300 | × \times × | √ \surd √ |
16 | 300 300 300 | 0 0 0 | 20 20 20 | × \times × | × \times × |
17 | 300 300 300 | 1 1 1 | 100 100 100 | × \times × | × \times × |
18 | 300 300 300 | 2 2 2 | 300 300 300 | √ \surd √ | √ \surd √ |
19 | 300 300 300 | 300 300 300 | 300 300 300 | × \times × | √ \surd √ |
20 | 2000 2000 2000 | 0 0 0 | 20 20 20 | × \times × | × \times × |
21 | 2000 2000 2000 | 1 1 1 | 20 20 20 | × \times × | × \times × |
22 | 2000 2000 2000 | 2 2 2 | 100 100 100 | × \times × | × \times × |
23 | 2000 2000 2000 | 2000 2000 2000 | 100 100 100 | × \times × | × \times × |
24 | 2000 2000 2000 | 2000 2000 2000 | 300 300 300 | × \times × | × \times × |
25 | 2000 2000 2000 | 2000 2000 2000 | 300 300 300 | × \times × | × \times × |
特殊性质 1:图上任意不同的两点 u , v u,v u,v 间,存在一条耗费体力最少的路径只包含一条道路。
特殊性质 2:对于所有的 1 ≤ i ≤ n , k i = 1 1≤ i≤ n,\ k_i= 1 1≤i≤n, ki=1。
我们首先将最短路径求出来,显然这是一道dp。具体实现如下:
定义 d p i , j dp_{i,j} dpi,j为前 i i i个换了 j j j次的期望值,发现不好转移。考虑再加一位状态,定义 d p i , j , 1 / 0 dp_{i,j,1/0} dpi,j,1/0为前 i i i个换了 j j j次第 i i i个换/不换的期望值。
分别考虑对 d p i , j , 1 / 0 dp_{i,j,1/0} dpi,j,1/0的转移:
首先考虑对 d p i , j , 0 dp_{i,j,0} dpi,j,0的转移,当前这位不换,上一位可换可不换。当上一位不换时,直接加上没换的距离即可。当上一位换了的时候,它有可能换成功,概率为 k i − 1 k_{i-1} ki−1,也有可能换失败,其概率为 1 − k i − 1 1-k_{i-1} 1−ki−1。
即:
再考虑对 d p i , j , 1 dp_{i,j,1} dpi,j,1的转移。上一位还是可换可不换。当上一位不换时,当前这一位可能换成功可能换失败。当上一位要换时,这两位都有可能换成功或换失败。
由dp定义可得:
由于题目上说可以不选,直接找 d p n , i , 0 / 1 ( i ϵ [ 0 , m ] ) dp_{n,i,0/1}(i \epsilon [0,m]) dpn,i,0/1(iϵ[0,m])的最小值即可。
#include
using namespace std;
#define pi pair<int,int>
const int N=2e3+5,M=2e5+5;
int n,m,v,e;
int c[N],d[N],a[N];
double k[N];
int h[M],to[M],w[M],ne[M],tot=0;
void add(int u,int v,int d){
tot++;to[tot]=v;w[tot]=d;ne[tot]=h[u];h[u]=tot;
}
int dis[N][N],vis[N];
void dij(int o){//dijkstra求最短路
memset(vis,0,sizeof(vis));
memset(dis[o],0x3f,sizeof(dis[o]));
dis[o][o]=0;priority_queue<pi,vector<pi>,greater<pi>>q;
q.push({0,o});
while(q.size()){
int u=q.top().second;q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=h[u];i;i=ne[i]){
int v=to[i];
if(dis[o][u]+w[i]<dis[o][v]){
dis[o][v]=dis[o][u]+w[i];
if(!vis[v]){q.push({dis[o][v],v});}
}
}
}
}
double dp[N][N][2];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m>>v>>e;
for(int i=1;i<=n;i++)cin>>c[i];
for(int i=1;i<=n;i++)cin>>d[i];
for(int i=1;i<=n;i++)cin>>k[i];
for(int i=1;i<=e;i++){
int x,y,z;
cin>>x>>y>>z;add(x,y,z);add(y,x,z);
}
for(int i=1;i<=v;i++)dij(i);//求出最短路
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)dp[i][j][0]=dp[i][j][1]=0x3f3f3f3f;//dp赋初值,由于是实数类型,只能这么赋值
dp[1][0][0]=dp[1][1][1]=0;//边界
for(int i=1;i<=v;i++)dis[0][i]=0;
for(int i=2;i<=n;i++){
dp[i][0][0]=dp[i-1][0][0]+dis[c[i-1]][c[i]];//边界
for(int j=1;j<=min(i,m);j++){
dp[i][j][0]=min(dp[i][j][0],dp[i-1][j][0]+dis[c[i-1]][c[i]]);//不选直接更行
dp[i][j][0]=min(dp[i][j][0],dp[i-1][j][1]+k[i-1]*dis[d[i-1]][c[i]]/*选成功*/+(1-k[i-1])*dis[c[i-1]][c[i]]/*选失败*/);//要选有概率选失败
dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][0]+k[i]*dis[c[i-1]][d[i]]/*成功*/+(1-k[i])*dis[c[i-1]][c[i]]/*失败*/);//当前这位有概率失败
dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][1]+k[i-1]*k[i]*dis[d[i-1]][d[i]]/*i,i-1成功*/+(1-k[i-1])*k[i]*dis[c[i-1]][d[i]]/*i成功*/+(1-k[i])*k[i-1]*dis[d[i-1]][c[i]]/*i-1成功*/+(1-k[i])*(1-k[i-1])*dis[c[i-1]][c[i]]/*都不成功*/);
//两次都有概率失败
}
}
double ans=0x3f3f3f3f;
for(int i=0;i<=m;i++)ans=min(ans,min(dp[n][i][0],dp[n][i][1]));//找最小值
cout<<fixed<<setprecision(2)<<ans;
return 0;
}