1. 程式人生 > >Luogu P1850 換教室(期望dp)

Luogu P1850 換教室(期望dp)

P1850 換教室

題意

題目描述

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

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

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

上課。

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

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

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

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

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

輸入輸出格式

輸入格式:

第一行四個整數\(n,m,v,e\)\(n\)表示這個學期內的時間段的數量;\(m\)表示牛牛最多可以申請更換多少節課程的教室;\(v\)表示牛牛學校裡教室的數量;\(e\)

表示牛牛的學校裡道路的數量。

第二行\(n\)個正整數,第\(i(1\leq i\leq n)\)個正整數表示\(c_i\),即第\(i\)個時間段牛牛被安排上課的教室;保證\(1\leq c_i\leq v\)

第三行\(n\)個正整數,第\(i(1\leq i\leq n)\)個正整數表示\(d_i\),即第\(i\)個時間段另一間上同樣課程的教室;保證\(1\leq d_i\leq v\)

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

接下來\(e\)行,每行三個正整數\(a_j,b_j,w_j\),表示有一條雙向道路連線教室\(a_j,b_j\),通過這條道路需要耗費的體力值是\(w_j\);保證\(1\leq a_j,b_j\leq v\)\(1\leq w_j\leq 100\)

保證\(1\leq n\leq 2000\)\(0\leq m\leq 2000\)\(1\leq v\leq 300\)\(0\leq e\leq 90000\)

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

保證輸入的實數最多包含\(3\)位小數。

輸出格式:

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

測試資料保證四捨五入後的答案和準確答案的差的絕對值不大於\(4\times 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說明】

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

P1850-1

【提示】

道路中可能會有多條雙向道路連線相同的兩間教室。 也有可能有道路兩端連線的是同一間教室。

請注意區分\(n,m,v,e\)的意義,\(n\)不是教室的數量,\(m\)不是道路的數量。

P1850-2

特殊性質\(1\):圖上任意兩點\(a_i,b_i,a_i\neq b_i\)間,存在一條耗費體力最少的路徑只包含一條道路。

特殊性質\(2\):對於所有的\(1\leq i\leq n\)\(k_i=1\)

思路

很容易想出,狀態\(dp[i][j]\)表示前\(i\)節課,在換了\(j\)節課之後所耗費的體力期望。可是我們該怎麼轉移呢?

想想我們應該需要些什麼。首先,我們肯定要知道任意兩間教室之間的最短距離,這個可以用\(Floyd\)演算法做到。其次,我們需要知道第\(i-1\)節課最終在那間教室上課,這樣才能從\(i-1\)轉移到\(i\)。既然任意一節課只可能有兩間教室,我們直接改寫狀態為\(dp[i][j][0/1]\),最後一維的\(0/1\)表示第\(i\)節課的教室有沒有更換。

現在再來考慮轉移方程。如果兩節課都沒有換,那就直接把上一個狀態加上距離得到當前狀態:

dp[i][j][0]=dp[i-1][j][0]+dis[c[i-1]][c[i]];

當然,還有可能上一節課換了教室,就不能這樣直接轉移:

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

\(k\)是換教室成功的概率。如果換成功了,那麼就從\(d[i-1]\)走到\(c[i]\),否則還是從\(c[i-1]\)走到\(c[i]\)。乘上概率,得到期望。

那麼這節課換了教室的情況是類似的,只不過更長更難寫:

dp[i][j][1]=min(dp[i-1][j-1][0]+dis[c[i-1]][c[i]]*(1-k[i])+dis[c[i-1]][d[i]]*k[i],dp[i-1][j-1][1]+dis[d[i-1]][d[i]]*k[i-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[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]));

轉移到最後,\(dp[n][0\sim m][0/1]\)的最小值就是答案啦。

AC程式碼

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int n,m,v,e,c[MAXN],d[MAXN];
double ans=DBL_MAX,k[MAXN],dis[305][305],dp[MAXN][MAXN][2];
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]);
    for(int i=1;i<=v;i++)
        for(int j=1;j<=v;j++)
            dis[i][j]=(i==j?0:0x3f3f3f3f);
    while(e--)
    {
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        dis[x][y]=dis[y][x]=min(dis[x][y],double(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]);
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            dp[i][j][0]=dp[i][j][1]=0x3f3f3f3f;
    dp[1][0][0]=dp[1][1][1]=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],min(dp[i-1][j][0]+dis[c[i-1]][c[i]],dp[i-1][j][1]+dis[c[i-1]][c[i]]*(1-k[i-1])+dis[d[i-1]][c[i]]*k[i-1]));
            dp[i][j][1]=min(dp[i][j][1],min(dp[i-1][j-1][0]+dis[c[i-1]][c[i]]*(1-k[i])+dis[c[i-1]][d[i]]*k[i],dp[i-1][j-1][1]+dis[d[i-1]][d[i]]*k[i-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[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i])));
        }
    }
    for(int i=0;i<=m;i++) ans=min(ans,min(dp[n][i][0],dp[n][i][1]));
    printf("%.2f",ans);
    return 0;
}