bzoj 4899 記憶的輪廓 題解(概率dp+決策單調性優化)

Barca_10發表於2019-06-23

題目背景

四次死亡輪迴後,昴終於到達了賢者之塔,當代賢者夏烏拉一見到昴就上前抱住了昴“師傅!你終於回來了!你有著和師傅一樣的魔女的餘香,肯定是師傅”。
眾所周知,大賢者是嫉妒魔女沙提拉的老公,400年前與神龍、劍聖一起封印魔女因子暴走的莎緹拉。在魔女茶會的時候,莎緹拉也表示過對昴濃濃的愛意,昴便是被莎緹拉召喚來異世界的。
而賢者之塔中的資料與試煉,似乎都指向同一種可能性……記憶的輪廓,逐漸顯形……

題目描述

通往賢者之塔的路上,有許多的危機。
我們可以把這個地形看做是一顆樹,根節點編號為1,目標節點編號為n,其中1-n的簡單路徑上,編號依次遞增,在[1,n]中,一共有n個節點。
我們把編號在[1,n]的叫做正確節點,[n+1,m]的叫做錯誤節點。一個葉子,如果是正確節點則為正確葉子,否則稱為錯誤葉子。
莎緹拉要幫助昴到達賢者之塔,因此現在面臨著存檔位置設定的問題。為了讓昴成長為英雄,因此一共只有p次存檔的機會,其中1和n必須存檔。被莎緹拉設定為要存檔的節點稱為存檔位置。
當然不能讓昴陷入死迴圈,所以存檔只能在正確節點上進行,而且同一個節點不能存多次檔。因為通往賢者之塔的路上有影響的瘴氣,因此莎緹拉假設昴每次位於樹上一個節點時,都會等概率選擇一個兒子走下去。每當走到一個錯誤葉子時,再走一步就會讀檔。
具體的,每次昴到達一個新的存檔位置,存檔點便會更新為這個位置(假如現在的存檔點是i,現在走到了一個存檔位置j>i,那麼存檔點便會更新為j)。讀檔的意思就是回到當前存檔點。
初始昴位於1,當昴走到正確葉子n時,便結束了路程。莎緹拉想知道,最優情況下,昴結束路程的期望步數是多少?
輸入格式

第一行一個正整數T表示資料組數。
接下來每組資料,首先讀入三個正整數n,m,p。
接下來m-n行,描述樹上所有的非正確邊(正確邊即連線兩個正確節點的邊),用兩個正整數j,k表示j與k之間有一條連邊,j和k可以均為錯誤節點,也可以一個為正確節點另一個為錯誤節點。資料保證j是k的父親。
輸出格式

T行每行一個實數表示每組資料的答案。請保留四位小數。

樣例輸入


1
3 7 2
1 4
2 5
3 6
3 7

樣例輸出

9.000

資料範圍及約定

50%,n=p
70%,50<=p<=n<=500
100%,50<=p<=n<=700,m<=1500,T<=5
資料保證每個除了n的正確節點均有至少2個兒子,至多3個兒子。

---------------------------------------------------------------分界線---------------------------------------------------------------

考試T2,調考前剛qj過改過,確實是一道毒瘤題好題,考試時時間不夠看都沒看考完試才開始做了這題。

理解題理解了一節課

做題先看資料範圍,否則涼涼。

我們可以看到有50%的資料是n=p的,對於n=p的情況,我們不難分析出每個點都存檔是最優解,這樣情況就簡單很多。

接下來我們考慮怎麼轉移。

設個g[i]為對於一個錯誤節點i還要走多少步會存檔。

g[i]=1+∑g[j]/du[i](j是i的兒子)。一遍dfs就可以處理出來g陣列。

我們再處理陣列sum,sum[i]=∑g[j](j是i的錯誤兒子)。

設f[i]表示正確節點i走到n的期望步數,顯然f[n]=0,我們倒著遞推。
f[i]=1+1/d[i]*f[i+1]+1/d[i]*sigma{g[j]+f[i]}[j是i的錯誤兒子]
移項得f[i]=d[i]+f[i+1]+s[i]。

over,50pts到手。

接下來我們考慮把它優化到70pts。

設dp(i,j)表示存檔點在i還有j次存檔機會的最優解。

設a(i,j)表示存檔點在i,從i走到正確節點j的最少期望步數。

首先我們可以o(n2)把a陣列處理出來。

a(i,j)=a(i,j-1)+1+1/du(j-1)×∑(a(i,j)+g(k)){k是j-1的錯誤兒子}。

整理移項得a(i,j)=du(i,j-1)×a(i,j-1)+sum(j-1)+du(j-1)。

然後我們列舉存檔點k,則dp(i,j)可以由dp(k,j-1)和a(i,k)轉移。

時間複雜度O(n2p),70pts到手。

最後我們來考慮正解。其實博主並不會正解。

還是放直鏈吧。%%%出題人。

https://blog.csdn.net/WerKeyTom_FTD/article/details/53026266

出題人給出了三種正解。

由於第二種看起來十分好寫比較優秀,博主選擇了第二種。

到現在博主還是很mengbi,在這裡就不給予講解了。

如果有時間的話博主也會用其他兩種方法A掉這題的。

下面是三個分數段的程式碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
const int N=3005;
using namespace std;
int first[N],nex[N],to[N],tot,vis[N],du[N];double sum[N],g[N],f[N];
void add(int a,int b){
    to[++tot]=b;nex[tot]=first[a];first[a]=tot;
}
void dfs(int x){
    g[x]=1.0;vis[x]=1;
    for(int i=first[x];i;i=nex[i]){
        int y=to[i];
        dfs(y);
        g[x]+=1.0/du[x]*g[y];
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        memset(du,0,sizeof(du));
        //memset(sum,0,sizeof(sum));
        memset(g,0,sizeof(g));tot=0;
        int n,m,p;
        scanf("%d%d%d",&n,&m,&p);
        for(int i=1;i<=m-n;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b);
            du[a]++;
        }
        for(int i=1;i<=n;i++) du[i]++;
        for(int i=n+1;i<=m;i++){
            if(vis[i]) continue;
            dfs(i);
        }
        for(int i=1;i<=n;i++){
            sum[i]=0.0;
            for(int j=first[i];j;j=nex[j]){
                //if(j>n&&j<=m)
                if(to[j]>n&&to[j]<=m)
                sum[i]+=g[to[j]];
            }
        }
        f[n]=0.0;
        for(int i=n-1;i>=1;i--){
            f[i]=f[i+1]+sum[i]+du[i];
            //cout<<g[i]<<" ";
        }
        //for(int i=n+1;i<=m;i++) /*cout<<i<<" ",*/printf("%.4lf ",g[i]);
        printf("%.4lf\n",f[1]);
    }
}
50pts
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define R register
inline int read(){
    R int aa=0,bb=1;char cc=getchar();
    while(cc<'0'||cc>'9')
        {if(cc=='-')bb=-1;cc=getchar();}
    while(cc<='9'&&cc>='0')
        {aa=aa*10+cc-'0';cc=getchar();}
    return aa*bb;
}
const int N=703;
const int M=1503;
struct tree{
    int v,last;
}tr[M*2];
int tot=0,first[M],du[M];
void add(int x,int y){
    tr[++tot].v=y;
    tr[tot].last=first[x];
    first[x]=tot;
    du[x]++;
}
int T,n,m,p;
bool vi[M];
double g[M],sum[N],f[N][N],fg[N][N],fi[N];
void dfs(int x){
    if(vi[x]) return;
    g[x]=1.0; vi[x]=1;
    for(R int i=first[x],v;i;i=tr[i].last){
        v=tr[i].v;
        dfs(v);
        g[x]+=1.0/du[x]*g[v];
    }
}
int main(){
    T=read();
    while(T--){
        memset(vi,0,sizeof(vi));
        memset(du,0,sizeof(du));
        memset(first,0,sizeof(first));
        tot=0;
        n=read();m=read();p=read();
        for(R int i=1,x,y;i<=m-n;i++){
            x=read();y=read();
            add(x,y);
        }
        for(R int i=1;i<=n;i++)du[i]++;
        for(R int i=n+1;i<=m;i++) if(!vi[i]) dfs(i);
        for(R int i=1;i<=n;i++){
            sum[i]=0.0;
            for(R int j=first[i],v;j;j=tr[j].last){
                v=tr[j].v;
                sum[i]+=1.0*g[v];
            }
        }
        if(n==p){
            fi[n]=0.0;
            for(R int i=n-1;i>=1;i--)
                fi[i]=(double)(du[i]+fi[i+1]+sum[i]);
            printf("%.4lf\n",fi[1]);
            continue;
        }
        
        for(R int i=1;i<=n;i++){
            fg[i][i]=0.0;
            for(R int j=i+1;j<=n;j++){
                fg[i][j]=fg[i][j-1]*du[j-1]+du[j-1]+sum[j-1];
            }
        }
        for(R int i=1;i<=n;i++)
            for(R int j=0;j<=p;j++)
                f[i][j]=0x7ffffff;
        for(R int i=0;i<=p;i++)  f[n][i]=0.0;
        for(R int i=n-1;i>=1;i--){
            for(R int j=1;j<p;j++){
                for(R int k=i+1;k<=n;k++){
                    f[i][j]=min( f[k][j-1]+fg[i][k], f[i][j]);
                }
            }
        }
        printf("%.4lf\n",f[1][p-1]);
    }
    return 0;
}
70pts
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define R register
inline int read()
{
    R int aa=0,bb=1;char cc=getchar();
    while(cc<'0'||cc>'9')
        {if(cc=='-')bb=-1;cc=getchar();}
    while(cc<='9'&&cc>='0')
        {aa=aa*10+cc-'0';cc=getchar();}
    return aa*bb;
}
const int N=703;
const int M=1503;
struct tree{
    int v,last;
}tr[M*2];
int tot=0,first[M],du[M];
void add(int x,int y)
{
    tr[++tot].v=y;
    tr[tot].last=first[x];
    first[x]=tot;
    du[x]++;
}
int T,n,m,p;
bool vi[M];
double g[M],sum[N],f[N][N],fg[N][N],fi[N];
void dfs(int x)
{
    if(vi[x]) return;
    g[x]=1.0; vi[x]=1;
    for(R int i=first[x],v;i;i=tr[i].last){
        v=tr[i].v;
        dfs(v);
        g[x]+=1.0/du[x]*g[v];
    }
}
int main()
{
    T=read();
    while(T--){
        memset(vi,0,sizeof(vi));
        memset(du,0,sizeof(du));
        memset(first,0,sizeof(first));
        tot=0;
        n=read();m=read();p=read();
        for(R int i=1,x,y;i<=m-n;i++){
            x=read();y=read();
            add(x,y);
        }
        for(R int i=1;i<=n;i++)du[i]++;
        for(R int i=n+1;i<=m;i++) if(!vi[i]) dfs(i);
        for(R int i=1;i<=n;i++){
            sum[i]=0.0;
            for(R int j=first[i],v;j;j=tr[j].last){
                v=tr[j].v;
                sum[i]+=1.0*g[v];
            }
        }
        if(n==p){
            fi[n]=0.0;
            for(R int i=n-1;i>=1;i--)
                fi[i]=(double)(du[i]+fi[i+1]+sum[i]);
            printf("%.4lf\n",fi[1]);
            continue;
        }
        
        for(R int i=1;i<=n;i++){
            fg[i][i]=0.0;
            for(R int j=i+1;j<=n;j++){
                fg[i][j]=fg[i][j-1]*du[j-1]+du[j-1]+sum[j-1];
            }
        }
        for(R int i=1;i<=n;i++)
            for(R int j=0;j<=p;j++)
                f[i][j]=0x7ffffff;
        for(R int i=0;i<=p;i++)  f[n][i]=0.0;
        for(R int i=n-1;i>=1;i--){
            for(R int j=1;j<p;j++){
                int r=min(i+40,n);
                for(R int k=i+1;k<=r;k++){
                    f[i][j]=min( f[k][j-1]+fg[i][k], f[i][j]);
                }
            }
        }
        printf("%.4lf\n",f[1][p-1]);
    }
    return 0;
}
AC

 

相關文章