夢開始的地方(Noip模擬3) 2021.5.24
T1 景區路線規劃(期望dp/記憶化搜尋)
遊樂園被描述成一張n個點,m條邊的無向圖(無重邊,無自環)。每個點代表一個娛樂專案,第i個娛樂專案需要耗費c[i]分鐘的時間,會讓小ma和妹子的開心度分別增加h1[i],h2[i],他們倆初始的開心度都是0。每條邊代表一條路,第i條邊連線編號為x[i],y[i]的兩個娛樂專案,從x[i]走到y[i]或者從y[i]走到x[i]耗費的時間都是t[i]分鐘。小ma和妹子預計在遊樂園裡玩k分鐘。最開始的時候,小ma和妹子會等概率的隨機選擇一個娛樂專案開始玩,每玩完一個專案後,小ma和妹子會等概率的隨機選擇一個可以從當前專案直達的且來得及玩的專案作為下一個專案。如果玩完一個專案後周圍沒有可以直達的且來得及玩的專案,小ma和妹子就會提前結束遊玩。 請你分別計算小ma和妹子在遊玩結束後開心度的期望
輸入第一行給出三個空格隔開的整數,分別表示n,m,k;
接下來的n行,每行三個空格隔開的整數,分別表示c[i],h1[i],h2[i];
接下來的m行,每行三個空格隔開的整數,分別表示x[i],y[i],t[i];
輸出兩個用空格隔開的實數,分表表示小ma和妹子開心度的期望,精確到小數點後 5 位。
a.in:
5 4 60
25 12 83
30 38 90
16 13 70
22 15 63
50 72 18
2 1 7
3 1 7
4 3 1
5 3 10
a.out
39.20000 114.40000
一看題目發現肯定是概率期望題,再仔細想想這三天做的題,就知道是個期望dp。
考試思路(錯):
因為聰聰與可可的10分打法根深蒂固,導致在考試時想到了用深搜(就不知道當時為什麼沒想到用那個題的正解思路.....)
深搜其實打的也挺對的,應該是能拿到55分(因為沒用記憶化和陣列記錄更新狀態),可是還犯了一個致命錯誤: 出度沒有找對!!
看題:每玩完一個專案後,小y和妹子會等概率的隨機選擇一個可以從當前專案直達的且來得及玩的專案作為下一個專案。
意思就是每次的出度會可能跟剛開始初始化記錄的出度不統一,於是應每次都找出度:
for(int i=r[st];i;i=e[i].next){
int v=e[i].to;
if(time+e[i].value+s[v].c>k) continue;
out++;
}
每次的概率都是之前的概率連乘出來的。
這樣就可以寫出來了:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int NN=10005; 4 int n,m,k,tmp; 5 double hh1,hh2,H1[105][485],H2[105][485]; 6 struct node{ 7 int c,h1,h2; 8 };node s[105]; 9 struct SNOW{ 10 int to,next,value; 11 };SNOW e[NN]; int tot,r[NN]; 12 inline void add(int x,int y,int z){ 13 e[++tot]=(SNOW){y,r[x],z}; 14 r[x]=tot; 15 } 16 inline double dfs1(int st,int time){ 17 int out=0; 18 if(time>k) return 0.0; 19 if(H1[st][time]) return H1[st][time]; 20 for(int i=r[st];i;i=e[i].next){ 21 int v=e[i].to; 22 if(time+e[i].value+s[v].c>k) continue; 23 out++; 24 } 25 H1[st][time]=s[st].h1; 26 if(!out) return H1[st][time]; 27 for(int i=r[st];i;i=e[i].next){ 28 int v=e[i].to; 29 H1[st][time]+=dfs1(v,time+e[i].value+s[v].c)/out; 30 } 31 return H1[st][time]; 32 } 33 inline double dfs2(int st,int time){ 34 int out=0; 35 if(time>k) return 0.0; 36 if(H2[st][time]) return H2[st][time]; 37 for(int i=r[st];i;i=e[i].next){ 38 int v=e[i].to; 39 if(time+e[i].value+s[v].c>k) continue; 40 out++; 41 } 42 H2[st][time]=s[st].h2; 43 if(!out) return H2[st][time]; 44 for(int i=r[st];i;i=e[i].next){ 45 int v=e[i].to; 46 H2[st][time]+=dfs2(v,time+e[i].value+s[v].c)/out; 47 } 48 return H2[st][time]; 49 } 50 namespace WSN{ 51 inline int main(){ 52 scanf("%d%d%d",&n,&m,&k); 53 for(int i=1;i<=n;i++) 54 scanf("%d%d%d",&s[i].c,&s[i].h1,&s[i].h2); 55 for(int i=1,x,y,z;i<=m;i++){ 56 scanf("%d%d%d",&x,&y,&z); 57 add(x,y,z); add(y,x,z); 58 } 59 for(int i=1;i<=n;i++){ 60 hh1+=dfs1(i,s[i].c)/n; 61 hh2+=dfs2(i,s[i].c)/n; 62 } 63 printf("%.5lf %.5lf",hh1,hh2); 64 return 0; 65 } 66 } 67 signed main(){return WSN::main();}View Code
小結:
概率期望專題裡這種題型不少見,感覺比聰聰與可可簡單一些,還是要記住正解程式碼呀!!!
T2 [中山市選2009] 樹(樹規/高斯消元)
圖論中的樹為一個無環的無向圖。給定一棵樹,每個節點有一盞指示燈和一個按鈕。如果節點的按扭被按了,那麼該節點的燈會從熄滅變為點亮(當按之前是熄滅的),或者從點亮到熄滅(當按之前是點亮的)。並且該節點的直接鄰居也發生同樣的變化。 開始的時候,所有的指示燈都是熄滅的。請程式設計計算最少要按多少次按鈕,才能讓所有節點的指示燈變為點亮狀態。
輸入檔案有多組資料。
輸入第一行包含一個整數n,表示樹的節點數目。每個節點的編號從1到n。
輸入接下來的n – 1行,每一行包含兩個整數x,y,表示節點x和y之間有一條無向邊。
當輸入n為0時,表示輸入結束。
對於每組資料,輸出最少要按多少次按鈕,才能讓所有節點的指示燈變為點亮狀態。每一組資料獨佔一行。
a.in:
3
1 2
1 3
0
a.out:
1
看到題目第一個肯定想到高斯消元,資料也比較合適,剛好也特別像專題裡的開關問題,於是。。。我們講講正解的樹規(俏展了)
狀態陣列:dp[n][2][2]表示第n個點操作(摁或不摁)後的狀態(開或關);
狀態轉移也分成四種情況來搞,就差不多了。
最終狀態:min(dp[1][1][1],dp[1][0][1]);
其他の注意 :
1.建邊的陣列開二倍(要不然MLE40);
2.要提前將dfs的點的狀態值儲存,要不然以後轉移狀態會串數。。。
3.不能將兩種暫時不合法狀態賦太大的值,否則轉移會爆long long
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int NN=105; 4 int n,g[105][2][2];//i->節點x 0/1->不按/按x 0/1->不開/開x 5 struct SNOW{ 6 int to,next; 7 };SNOW e[NN<<1]; int tot,r[NN<<1]; 8 inline void add(int x,int y){ 9 e[++tot]=(SNOW){y,r[x]}; 10 r[x]=tot; 11 } 12 inline void snow(int x,int fa){ 13 g[x][0][0]=0; g[x][1][1]=1; g[x][1][0]=n+1; g[x][0][1]=n+1; 14 for(int i=r[x];i;i=e[i].next){ 15 int v=e[i].to; 16 if(v==fa) continue; 17 snow(v,x); 18 int g00=g[x][0][0],g01=g[x][0][1],g10=g[x][1][0],g11=g[x][1][1];//提前記錄,否則狀態轉移時值會改變 19 g[x][0][0]=min(g01+g[v][1][1],g00+g[v][0][1]); 20 g[x][0][1]=min(g00+g[v][1][1],g01+g[v][0][1]); 21 g[x][1][0]=min(g11+g[v][1][0],g10+g[v][0][0]); 22 g[x][1][1]=min(g10+g[v][1][0],g11+g[v][0][0]); 23 } 24 } 25 namespace WSN{ 26 inline int main(){ 27 while(1){ 28 memset(g,0,sizeof(g)); 29 memset(r,0,sizeof(r)); 30 tot=0; 31 scanf("%d",&n); 32 if(n==0) break; 33 for(int i=1,x,y;i<n;i++){ 34 scanf("%d%d",&x,&y); 35 add(x,y); add(y,x); 36 } 37 snow(1,0); 38 printf("%d\n",min(g[1][1][1],g[1][0][1])); 39 } 40 return 0; 41 } 42 } 43 signed main(){return WSN::main();}View Code
附上特殊的高斯消元打法
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int NN=105; 4 int n,g[NN][NN],wsn; 5 inline void Guass(){ 6 int cnt=1; 7 for(int i=1;i<=n;i++){ 8 int r=cnt; 9 for(int j=cnt+1;j<=n;j++) if(g[j][i]>g[r][i]) r=j; 10 if(!g[r][i]) continue; 11 if(r!=cnt) for(int j=i;j<=n+1;j++) swap(g[r][j],g[cnt][j]); 12 for(int j=1;j<=n;j++) 13 if(j!=cnt&&g[j][i])//將所有的係數都變歷一遍,保證只剩下自由元 14 for(int k=i;k<=n+1;k++) g[j][k]^=g[cnt][k]; 15 cnt++; 16 } 17 } 18 inline void dfs(int x,int line){//暴力搜尋自由元 19 if(line>=wsn) return; 20 if(!x){ wsn=line; return;} 21 if(g[x][x]) dfs(x-1,line+g[x][n+1]); 22 else{ 23 if(g[x][n+1]) return; 24 dfs(x-1,line); 25 for(int i=x;i>=1;i--) g[i][n+1]^=g[i][x]; 26 dfs(x-1,line+1); 27 for(int i=x;i>=1;i--) g[i][n+1]^=g[i][x]; 28 } 29 } 30 namespace WSN{ 31 inline int main(){ 32 while(1){ 33 wsn=0x7fffffff; 34 memset(g,0,sizeof(g)); 35 scanf("%d",&n); 36 if(n==0) break; 37 for(int i=1,x,y;i<n;i++){ 38 scanf("%d%d",&x,&y); 39 g[x][y]=g[y][x]=1; 40 } 41 for(int i=1;i<=n;i++) g[i][i]=g[i][n+1]=1; 42 Guass(); 43 dfs(n,0); 44 printf("%d\n",wsn); 45 } 46 return 0; 47 } 48 } 49 signed main(){return WSN::main();}View Code
T3 奇怪的道路(狀壓dp)
小宇從歷史書上了解到一個古老的文明。這個文明在各個方面高度發達,交通方面也不例外。考古學家已經知道,這個文明在全盛時期有n座城市,編號為1..n。m條道路連線在這些城市之間,每條道路將兩個城市連線起來,使得兩地的居民可以方便地來往。一對城市之間可能存在多條道路。 據史料記載,這個文明的交通網路滿足兩個奇怪的特徵。首先,這個文明崇拜數字K,所以對於任何一條道路,設它連線的兩個城市分別為u和v,則必定滿足1 <=|u - v| <= K。此外,任何一個城市都與恰好偶數條道路相連(0也被認為是偶數)。不過,由於時間過於久遠,具體的交通網路我們已經無法得知了。小宇很好奇這n個城市之間究竟有多少種可能的連線方法,於是她向你求助。 方法數可能很大,你只需要輸出方法數模1000000007後的結果。
輸入共一行,為3個整數n,m,K。
輸出1個整數,表示方案數模1000000007後的結果。
【輸入樣例1】
3 4 1
【輸入樣例2】
4 3 3
【輸出樣例1】
3
【輸出樣例2】
4
考試時候真的沒看出來是狀壓,那個1000000007看著太像排列組合裡的東西了(還是太弱。。。)
狀壓思路有點像動物園;
得出狀態轉移陣列: dp[i][j][u][t]表示第i個結點用了j條邊情況下的前u位連邊數狀態(奇數0,偶數1),i的前t個點是第四維開的意義:
思路:
1.迴圈的時候避免開頭幾位找的數大,用一個min(i,k)來卡住範圍;
2.如果不連: dp[i][j][u][t-1]+=dp[i][j][u][t];
3.如果連: dp[i][j+1][u^1^(1<<t)][t]+=dp[i][j][u][t];
4.2,3步將會把所有狀態轉移至第四維是0上;
5.如果開頭是偶數,可以進行下一個節點的轉移:dp[i+1][j][u<<1][min(i,k)]+=dp[i][j][u][0];
6.最終狀態: dp[n][m][0][0];
7.初始狀態: dp[1][0][0][0]=1;
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 const int momomo=1000000007; 5 int n,m,k,dp[35][35][1<<10][10]; 6 namespace WSN{ 7 inline int main(){ 8 scanf("%lld%lld%lld",&n,&m,&k); 9 dp[1][0][0][0]=1; 10 for(int i=1;i<=n;i++) 11 for(int j=0;j<=m;j++) 12 for(int u=0;u<(1<<k+1);u++){ 13 for(int t=min(i,k);t>=1;t--){ 14 dp[i][j][u][t-1]=(dp[i][j][u][t-1]+dp[i][j][u][t])%momomo; 15 dp[i][j+1][u^1^(1<<t)][t]=(dp[i][j+1][u^1^(1<<t)][t]+dp[i][j][u][t])%momomo; 16 } 17 if(!((u>>k)&1)) dp[i+1][j][u<<1][min(i,k)]=(dp[i+1][j][u<<1][min(i,k)]+dp[i][j][u][0])%momomo; 18 } 19 printf("%lld\n",dp[n][m][0][0]); 20 return 0; 21 } 22 } 23 signed main(){return WSN::main();}View Code
馬の思
由於種種原因吧,這次考的比較崩,這三道題改下來感覺自己拿100+還是可行的,畢竟第一題確實比較水。
搭嘎(但是),畢竟自己只是拿了9分,就沒什麼可吹噓的理由了,只能是暫且埋頭苦幹吧,
5.24已是歷史,抬頭往前看才有陽光。。。。暫且鼓勵自己一波
馬の想
SNack:
你們快集訓了吧,暑假要加油鴨!
小小馬:
肯定會的,並且一直想你。。