1. 程式人生 > >Luogu P1850 換教室

Luogu P1850 換教室

題目描述

對於剛上大學的牛牛來說,他面臨的第一個問題是如何根據實際情況申請合適的課程。

在可以選擇的課程中,有 2n2n 節課程安排在 nn 個時間段上。在第 ii(1 \leq i \leq n1≤i≤n)個時間段上,兩節內容相同的課程同時在不同的地點進行,其中,牛牛預先被安排在教室 c_ici​ 上課,而另一節課程在教室 d_idi​ 進行。

在不提交任何申請的情況下,學生們需要按時間段的順序依次完成所有的 nn 節安排好的課程。如果學生想更換第 ii節課程的教室,則需要提出申請。若申請通過,學生就可以在第 ii 個時間段去教室 d_idi​ 上課,否則仍然在教室 c_ici​ 上課。

由於更換教室的需求太多,申請不一定能獲得通過。通過計算,牛牛發現申請更換第 ii 節課程的教室時,申請被通過的概率是一個已知的實數 k_iki​,並且對於不同課程的申請,被通過的概率是互相獨立的。

學校規定,所有的申請只能在學期開始前一次性提交,並且每個人只能選擇至多 mm 節課程進行申請。這意味著牛牛必須一次性決定是否申請更換每節課的教室,而不能根據某些課程的申請結果來決定其他課程是否申請;牛牛可以申請自己最希望更換教室的 mm 門課程,也可以不用完這 mm 個申請的機會,甚至可以一門課程都不申請。

因為不同的課程可能會被安排在不同的教室進行,所以牛牛需要利用課間時間從一間教室趕到另一間教室。

牛牛所在的大學有 vv 個教室,有 ee 條道路。每條道路連線兩間教室,並且是可以雙向通行的。由於道路的長度和擁堵程度不同,通過不同的道路耗費的體力可能會有所不同。 當第 ii(1 \leq i \leq n-11≤i≤n−1)節課結束後,牛牛就會從這節課的教室出發,選擇一條耗費體力最少的路徑前往下一節課的教室。

現在牛牛想知道,申請哪幾門課程可以使他因在教室間移動耗費的體力值的總和的期望值最小,請你幫他求出這個最小值。

輸入輸出格式

輸入格式:

第一行四個整數 n,m,v,en,m,v,e。nn 表示這個學期內的時間段的數量;mm 表示牛牛最多可以申請更換多少節課程的教室;vv 表示牛牛學校裡教室的數量;ee表示牛牛的學校裡道路的數量。

第二行 nn 個正整數,第 ii(1 \leq i \leq n1≤i≤n)個正整數表示 c_ici​,即第 ii 個時間段牛牛被安排上課的教室;保證 1 \le c_i \le v1≤ci​≤v。

第三行 nn 個正整數,第 ii(1 \leq i \leq n1≤i≤n)個正整數表示 d_idi​,即第 ii 個時間段另一間上同樣課程的教室;保證 1 \le d_i \le v1≤di​≤v。

第四行 nn 個實數,第 ii(1 \leq i \leq n1≤i≤n)個實數表示 k_iki​,即牛牛申請在第 ii 個時間段更換教室獲得通過的概率。保證 0 \le k_i \le 10≤ki​≤1。

接下來 ee 行,每行三個正整數 a_j, b_j, w_jaj​,bj​,wj​,表示有一條雙向道路連線教室 a_j, b_jaj​,bj​,通過這條道路需要耗費的體力值是 w_jwj​;保證 1 \le a_j, b_j \le v1≤aj​,bj​≤v, 1 \le w_j \le 1001≤wj​≤100。

保證 1 \leq n \leq 20001≤n≤2000,0 \leq m \leq 20000≤m≤2000,1 \leq v \leq 3001≤v≤300,0 \leq e \leq 900000≤e≤90000。

保證通過學校裡的道路,從任何一間教室出發,都能到達其他所有的教室。

保證輸入的實數最多包含 33 位小數。

輸出格式:

輸出一行,包含一個實數,四捨五入精確到小數點後恰好22位,表示答案。你的輸出必須和標準輸出完全一樣才算正確。

測試資料保證四捨五入後的答案和準確答案的差的絕對值不大於 4 \times 10^{-3}4×10−3。 (如果你不知道什麼是浮點誤差,這段話可以理解為:對於大多數的演算法,你可以正常地使用浮點數型別而不用對它進行特殊的處理)

輸入輸出樣例

輸入樣例#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:圖上任意兩點 a_iai​, b_ibi​, a_iai​≠ b_ibi​間,存在一條耗費體力最少的路徑只包含一條道路。

特殊性質2:對於所有的 1≤ i≤ n1≤i≤n, k_i= 1ki​=1 。

這不很明顯嗎?

先floyd跑出任意兩點最短路

然後期望dp一下即可

#include<cstdio>
#include<iostream>
using namespace std;
 
int read()
{
    int ret=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return ret; 
}
 
const int N=2005,V=305;
int n,m,v,e,c[N],d[N];
double k[N],f[N][N][2],g[V][V],ans;
 
int main()
{
    n=read(),m=read(),v=read(),e=read();
    for(int i=1;i<=n;i++) c[i]=read();
    for(int i=1;i<=n;i++) d[i]=read();
    for(int i=1;i<=n;i++) scanf("%lf",&k[i]);
    for(int i=1;i<=v;i++)
        for(int j=1;j<=v;j++) g[i][j]=1e9;
    for(int i=1;i<=v;i++) g[i][i]=0;
    for(int i=1;i<=e;i++)
    {
        int a=read(),b=read();
        double c=read();
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    for(int K=1;K<=v;K++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<=v;j++)
                g[i][j]=min(g[i][j],g[i][K]+g[K][j]);
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            f[i][j][0]=f[i][j][1]=1e9;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
        {
            f[i][j][0]=min(f[i-1][j][0]+g[c[i-1]][c[i]],f[i-1][j][1]+g[c[i-1]][c[i]]*(1.000-k[i-1])+g[d[i-1]][c[i]]*k[i-1]);
            if(j) f[i][j][1]=min(f[i-1][j-1][0]+g[c[i-1]][c[i]]*(1.000-k[i])+g[c[i-1]][d[i]]*k[i],f[i-1][j-1][1]+g[c[i-1]][c[i]]*(1.000-k[i-1])*(1.000-k[i])+g[c[i-1]][d[i]]*(1.000-k[i-1])*k[i]+g[d[i-1]][c[i]]*(1.000-k[i])*k[i-1]+g[d[i-1]][d[i]]*k[i]*k[i-1]);
        }
    ans=(double)1e9;
    for(int i=0;i<=m;i++)
        ans=min(min(ans,f[n][i][0]),f[n][i][1]);
    printf("%.2lf",ans);
    return 0;
}