20171013校内训练
我们先仔细阅读题目,发现你最多能解锁的房间和移动的次数是一样的。这样,每次我们就可以解锁你要走过的k个房间,然后往前走
这样,我们会发现,从一个点开始,肯定是沿它往四个边界的距离的最小值(解锁完往前走就好了)走
由于第一次你要先走,所以先处理出每个点到S的距离,对于所有距离<=k的点,我们找到它到边界的最小距离,计算出需要的步数(一次走k步),然后更新答案。
记得最后答案要+1(因为你要先走一轮,然后才能解锁)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
不要试图把它边权取反然后跑最小割(因为最小割是S-T割,S和T必须分在不同的连通块内,而本题不用)
我们反过来思考,最后的图是连通的,这样,如果我们知道了最后的图,就能够算出边权和。
因为边权和最大,所以最后的图必须是两颗树(如果不是树,那么肯定能在此图的基础上删去一些边后使得它是一棵树,这样删去的边权和更大),且最后剩的两棵树的边权和要尽量小。
我们想到了什么?对,就是最小生成树。
但是最小生成树是一棵树啊?我们在树中任意删去一条边,它不就变为两颗树了吗?
为了使删去的边权和尽可能大,我们找到该最小生成树中边权最大的一条边,将其删去。
这样,我们把总边权减去剩下的两棵树的边权即为答案
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
期望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)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }