1. 程式人生 > >「NOIP2016」「P1850」 換教室(期望dp

「NOIP2016」「P1850」 換教室(期望dp

題目描述

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

在可以選擇的課程中,有 2n2n 節課程安排在 nn 個時間段上。在第 ii(1 \leq i \leq n1in)個時間段上,兩節內容相同的課程同時在不同的地點進行,其中,牛牛預先被安排在教室 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-11in1)節課結束後,牛牛就會從這節課的教室出發,選擇一條耗費體力最少的路徑前往下一節課的教室。

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

輸入輸出格式

輸入格式:

 

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

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

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

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

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

保證 1 \leq n \leq 20001n2000,0 \leq m \leq 20000m2000,1 \leq v \leq 3001v300,0 \leq e \leq 900000e90000。

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

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

 

輸出格式:

 

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

測試資料保證四捨五入後的答案和準確答案的差的絕對值不大於 4 \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:圖上任意兩點 a_iaib_ibia_iai≠ b_ibi間,存在一條耗費體力最少的路徑只包含一條道路。

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

題解

首先在讀題的時候沒弄清楚那個體力值是什麼鬼東西......以為是路上最大擁擠度為體力值呢(手動狗頭

就先跑一遍floyd預處理出兩點間的最小距離,

然後按照上課順序去dp。

設$f[i][j][0]$為在第$i$節課,已經申請了$j$次,當前沒有申請的最大期望;

$f[i][j][1]$為當前申請了的最大期望。

那麼轉移方程就很好寫了(吧

f[i][j][0]=min(f[i-1][j][0]+dis[c[i-1]][c[i]],
               f[i-1][j][1]+k[i-1]*dis[d[i-1]][c[i]]+(1-k[i-1])*dis[c[i-1]][c[i]]);

如果上節課沒有申請,那麼期望直接加上距離;

如果上節課申請了,那麼有$k_{i-1}$的機率上節課在$d_{i-1}$教室,有$1-k_{i-1}$的概率在$c_{i-1}$教室。

$f[i][j][1]$的情況數要多點兒,但也不難懂:

f[i][j][1]=min(f[i-1][j-1][0]+k[i]*dis[c[i-1]][d[i]]+(1-k[i])*dis[c[i-1]][c[i]],
               f[i-1][j-1][1]+k[i-1]*k[i]*dis[d[i-1]][d[i]]+k[i-1]*(1-k[i])*dis[d[i-1]][c[i]]
                             +(1-k[i-1])*k[i]*dis[c[i-1]][d[i]]+(1-k[i-1])*(1-k[i])*dis[c[i-1]][c[i]]);

然後按順序往下滾就行了。

初始化$f$陣列為INF,$f[1][0][0]=f[1][1][1]=0$。

//之前的寫法是先把$j=0$的情況預處理出來,不知道為什麼會88分,大概是訪問到了一些奇怪的點為0?

 1 /*
 2 qwerta 
 3 P1850 換教室 Accepted 
 4 100
 5 程式碼 C++,1.43KB
 6 提交時間 2018-11-06 21:29:35
 7 耗時/記憶體 2170ms, 65720KB
 8 */
 9 #include<iostream>
10 #include<cstring>
11 #include<cstdio>
12 #include<cmath>
13 using namespace std;
14 int c[2003];
15 int d[2003];
16 double k[2003];
17 struct emm{
18     int e,f,l;
19 }a[180003];
20 int h[303];
21 int tot=0;
22 int dis[303][303];
23 double s[2003][2003][2];
24 const double INF=1926081700000;
25 #define LL long long
26 int main()
27 {
28     //freopen("a.in","r",stdin);
29     int n,m,v,e;
30     cin>>n>>m>>v>>e;
31     if(n==1){cout<<"0.00";return 0;}
32     for(int i=1;i<=n;++i)
33     cin>>c[i];
34     for(int i=1;i<=n;++i)
35     cin>>d[i];
36     for(int i=1;i<=n;++i)
37     cin>>k[i];
38     //
39     memset(dis,127,sizeof(dis));//初始化dis為INF
40     //
41     for(int i=1;i<=e;++i)
42     {
43         int x,y;
44         int l;
45         cin>>x>>y>>l;
46         dis[x][y]=min(dis[x][y],l);//有重邊,所以取min
47         dis[y][x]=dis[x][y];
48     }
49     //floyd
50     for(int i=1;i<=v;++i)
51     dis[i][i]=0;
52     for(int z=1;z<=v;++z)
53     for(int i=1;i<=v;++i)
54     for(int j=1;j<=v;++j)
55     if(dis[i][j]>(LL)dis[i][z]+dis[z][j])
56       dis[i][j]=dis[i][z]+dis[z][j];
57     //
58     memset(s,127,sizeof(s));
59     s[1][0][0]=s[1][1][1]=0;
60     for(int j=0;j<=m;++j)//先迴圈j,好像反過來也沒什麼問題吧qwq
61     {
62         for(int i=2;i<=n;++i)
63         {
64             s[i][j][0]=min(s[i-1][j][0]+dis[c[i-1]][c[i]],
65                            s[i-1][j][1]+k[i-1]*dis[d[i-1]][c[i]]+(1-k[i-1])*dis[c[i-1]][c[i]]);
66             s[i][j][1]=min(s[i-1][j-1][0]+k[i]*dis[c[i-1]][d[i]]+(1-k[i])*dis[c[i-1]][c[i]],
67                            s[i-1][j-1][1]+k[i-1]*k[i]*dis[d[i-1]][d[i]]+k[i-1]*(1-k[i])*dis[d[i-1]][c[i]]
68                                          +(1-k[i-1])*k[i]*dis[c[i-1]][d[i]]+(1-k[i-1])*(1-k[i])*dis[c[i-1]][c[i]]);
69         }
70     }
71     double ans=INF;
72     for(int j=0;j<=m;++j)//不一定要用完申請機會
73     ans=min(ans,min(s[n][j][0],s[n][j][1]));
74     printf("%.2f",ans);
75     return 0;
76 }