bzoj 4899 記憶的輪廓 題解(概率dp+決策單調性優化)
題目背景
四次死亡輪迴後,昴終於到達了賢者之塔,當代賢者夏烏拉一見到昴就上前抱住了昴“師傅!你終於回來了!你有著和師傅一樣的魔女的餘香,肯定是師傅”。
眾所周知,大賢者是嫉妒魔女沙提拉的老公,400年前與神龍、劍聖一起封印魔女因子暴走的莎緹拉。在魔女茶會的時候,莎緹拉也表示過對昴濃濃的愛意,昴便是被莎緹拉召喚來異世界的。
而賢者之塔中的資料與試煉,似乎都指向同一種可能性……記憶的輪廓,逐漸顯形……
題目描述
通往賢者之塔的路上,有許多的危機。
我們可以把這個地形看做是一顆樹,根節點編號為1,目標節點編號為n,其中1-n的簡單路徑上,編號依次遞增,在[1,n]中,一共有n個節點。
莎緹拉要幫助昴到達賢者之塔,因此現在面臨著存檔位置設定的問題。為了讓昴成長為英雄,因此一共只有p次存檔的機會,其中1和n必須存檔。被莎緹拉設定為要存檔的節點稱為存檔位置。
當然不能讓昴陷入死迴圈,所以存檔只能在正確節點上進行,而且同一個節點不能存多次檔。因為通往賢者之塔的路上有影響的瘴氣,因此莎緹拉假設昴每次位於樹上一個節點時,都會等概率選擇一個兒子走下去。每當走到一個錯誤葉子時,再走一步就會讀檔。
具體的,每次昴到達一個新的存檔位置,存檔點便會更新為這個位置(假如現在的存檔點是i,現在走到了一個存檔位置j>i,那麼存檔點便會更新為j)。讀檔的意思就是回到當前存檔點。
輸入格式
第一行一個正整數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
資料保證每個除了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
&n