20171013校內訓練
我們先仔細閱讀題目,發現你最多能解鎖的房間和移動的次數是一樣的。這樣,每次我們就可以解鎖你要走過的k個房間,然後往前走
這樣,我們會發現,從一個點開始,肯定是沿它往四個邊界的距離的最小值(解鎖完往前走就好了)走
由於第一次你要先走,所以先處理出每個點到S的距離,對於所有距離<=k的點,我們找到它到邊界的最小距離,計算出需要的步數(一次走k步),然後更新答案。
記得最後答案要+1(因為你要先走一輪,然後才能解鎖)
#include<iostream> #include<cstdio> #include<cstring> using namespaceView Codestd; int step[1011][1011]; int x[4]={0,-1,0,1}; int y[4]={1,0,-1,0}; int q[1001001][2]; bool mp[1011][1011]; char c[1010]; int n,m,ans=999999999; void bfs(int xs,int ys) { memset(step,-1,sizeof(step)); step[xs][ys]=0; int head=0,tail=0;q[tail][0]=xs;q[tail][1]=ys;tail++; while(head<tail) {int xi=q[head][0],yi=q[head][1];head++; for(int i=0;i<=3;i++) { int xx=xi+x[i],yy=yi+y[i]; if(xx==0||yy==0||xx==n+1||yy==m+1)continue; if(!mp[xx][yy]&&step[xx][yy]==-1) { step[xx][yy]=step[xi][yi]+1; q[tail][0]=xx;q[tail][1]=yy;tail++; } } } } int main() { // freopen("room.in","r",stdin);freopen("room.out","w",stdout); int sx,sy; int k;scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++) { scanf("%s",c); for(int j=1;j<=m;j++) { if(c[j-1]==‘#‘)mp[i][j]=1; if(c[j-1]==‘S‘)mp[i][j]=0,sx=i,sy=j; if(c[j-1]==‘.‘)mp[i][j]=0; } } bfs(sx,sy); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { if(step[i][j]<=k&&step[i][j]!=-1) { int Min=min(i-1,min(j-1,min(n-i,m-j))); if(Min%k==0)ans=min(ans,Min/k); else ans=min(ans,Min/k+1); } } printf("%d",ans+1); return 0; }
不要試圖把它邊權取反然後跑最小割(因為最小割是S-T割,S和T必須分在不同的連通塊內,而本題不用)
我們反過來思考,最後的圖是連通的,這樣,如果我們知道了最後的圖,就能夠算出邊權和。
因為邊權和最大,所以最後的圖必須是兩顆樹(如果不是樹,那麽肯定能在此圖的基礎上刪去一些邊後使得它是一棵樹,這樣刪去的邊權和更大),且最後剩的兩棵樹的邊權和要盡量小。
我們想到了什麽?對,就是最小生成樹。
但是最小生成樹是一棵樹啊?我們在樹中任意刪去一條邊,它不就變為兩顆樹了嗎?
為了使刪去的邊權和盡可能大,我們找到該最小生成樹中邊權最大的一條邊,將其刪去。
這樣,我們把總邊權減去剩下的兩棵樹的邊權即為答案
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; struct xxx{ int u,v,cost; }g[101000]; int n,m,Max=-1,res=0; int fa[101000]; bool cmp(xxx a,xxx b){return a.cost<b.cost;} int Find(int x) { if(fa[x]==x)return x; return fa[x]=Find(fa[x]); } bool hb(int x,int y) { int xx=Find(x),yy=Find(y); if(xx==yy)return false; fa[xx]=yy;return true; } void kr() { sort(g+1,g+m+1,cmp); for(int i=1;i<=n;i++)fa[i]=i; for(int i=1;i<=m;i++)if(hb(g[i].u,g[i].v))res+=g[i].cost,Max=max(Max,g[i].cost); } int main() { freopen("cut.in","r",stdin);freopen("cut.out","w",stdout); int tot=0;scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%d",&g[i].u,&g[i].v,&g[i].cost); tot+=g[i].cost; } kr(); printf("%d",tot-(res-Max)); return 0; }View Code
期望DP。
由於這是我第一次打期望DP,我寫的詳細一點。
期望DP通常是逆推,即從結果推會初始。以此題為例。
我們發現,這個期望刷墻次數和墻的順序是沒有關系的,即設一開始有一行兩列,都沒被粉刷,你刷第一列和第二列是一樣的,都會有一列被粉刷且下一次刷沒有被粉刷的那一列的概率相等
這樣,我們便可以用dp[i][j]表示有i行的墻上有格子被粉刷,有j列的墻上有格子被粉刷的期望粉刷次數。
顯然,dp[n][m]=0。設初始有x行的墻上有格子被粉刷,有y列的墻上有格子被粉刷,則我們要求的是dp[x][y]。
我們思考如何轉移。
在dp[i][j]的基礎上再粉刷一個格子(為了方便說明,我們把已刷的i行j列格子移至左上角)
有(n-i)(m-j)/nm幾率推出dp[i+1][j+1](即下一個刷的格子落在紫色區域)
有(i*j)/nm幾率不變(即下一個刷的格子落在綠色區域)
有(n-i)j/nm幾率推出dp[i+1][j](即下一個刷的格子落在藍色區域)
有i(m-j)/nm幾率推出dp[i][j+1](即下一個刷的格子落在黃色區域)
由於把dp[n][m]不刷某些格子後得到dp[x][y]的不刷的次數與把dp[x][y]刷某些格子後得到dp[n][m]的次數一致
所以我們反過來思考,即把剛剛刷的格子刪掉。
若剛剛刷的格子落在紫色區域(概率為(n-i)(m-j)/nm),則把該格子不粉刷,i減小了1,j也減小了1,即dp[i][j]=dp[i+1][j+1]+1。
若剛剛刷的格子落在綠色區域(概率為(i*j)/nm),則把該格子不粉刷,卻沒有減小i或j,即dp[i][j]=dp[i][j]+1。
若剛剛刷的格子落在藍色區域(概率為(n-i)j/nm),則把該格子不粉刷,i減小了1,j不變,即dp[i][j]=dp[i+1][j]+1。
若剛剛刷的格子落在黃色區域(概率為i(m-j)/nm),則把該格子不粉刷,i不變,j減小了1,即dp[i][j]=dp[i][j+1]+1。
綜合起來看,dp[i][j]=dp[i+1][j]*(n-i)j/nm+dp[i][j+1]*i(m-j)/nm+dp[i][j]*(i*j)/nm+dp[i+1][j+1]*(n-i)(m-j)/nm+1
去分母,nmdp[i][j]=dp[i+1][j](n-i)j+dp[i][j+1]*i(m-j)+dp[i][j]*(i*j)+dp[i+1][j+1]*(n-i)(m-j)+nm
移項及合並同類項(把dp[i][j]移到左邊去),dp[i][j]*(nm-ij)=dp[i+1][j](n-i)j+dp[i][j+1]*i(m-j)+dp[i+1][j+1]*(n-i)(m-j)+nm
系數化為1,dp[i][j]=(dp[i+1][j](n-i)j+dp[i][j+1]*i(m-j)+dp[i+1][j+1]*(n-i)(m-j)+nm)/(nm-ij)
這就是dp公式啦。
總結一下:
期望dp通常逆推,即從結果推向初始狀態,也可以用記憶化搜索進行dp;
E=Σp1*(E1+X1)+Σp2*(E+X2)
其中E為當前狀態的期望,E1為下一個狀態的期望,p1和X1分別為將當前狀態轉移到下一個狀態的概率和花費,p2和X2分別為保持當前狀態的概率和花費。
最後化簡為E=(Σp1*(E1+X1)+Σp2*X2)/(1-Σp2)
#include<cstdio> #include<iostream> using namespace std; double dp[1101][1101]; int a[1101][1101]; using namespace std; int main() { // freopen("painter.in","r",stdin);freopen("painter.out","w",stdout); int ii=0,jj=0; int n,m;scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)scanf("%d",&a[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(a[i][j]){ii++;break;} for(int j=1;j<=m;j++) for(int i=1;i<=n;i++) if(a[i][j]){jj++;break;} dp[n][m]=0.00; for(int i=n;i>=ii;i--) for(int j=m;j>=jj;j--) { if(i==n&&j==m)continue; dp[i][j]=((double)dp[i+1][j]*(n-i)*j+(double)dp[i][j+1]*i*(m-j)+(double)dp[i+1][j+1]*(n-i)*(m-j)+(double)n*m)/(double)(n*m-i*j); } // for(int i=ii;i<=n;i++,cout<<endl) // for(int j=jj;j<=m;j++) // cout<<dp[i][j]<<" "; // cout<<dp[ii][jj]; printf("%.10lf",dp[ii][jj]); return 0; }View Code
20171013校內訓練