1. 程式人生 > >20171013校內訓練

20171013校內訓練

距離 黃色 names while blog 會有 one nbsp -a

技術分享

技術分享

技術分享

我們先仔細閱讀題目,發現你最多能解鎖的房間和移動的次數是一樣的。這樣,每次我們就可以解鎖你要走過的k個房間,然後往前走

這樣,我們會發現,從一個點開始,肯定是沿它往四個邊界的距離的最小值(解鎖完往前走就好了)走

由於第一次你要先走,所以先處理出每個點到S的距離,對於所有距離<=k的點,我們找到它到邊界的最小距離,計算出需要的步數(一次走k步),然後更新答案。

記得最後答案要+1(因為你要先走一輪,然後才能解鎖)

技術分享
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace
std; 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; }
View Code

技術分享

技術分享

技術分享

不要試圖把它邊權取反然後跑最小割(因為最小割是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校內訓練