20171013校内训练

Posted lh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20171013校内训练相关的知识,希望对你有一定的参考价值。

 

我们先仔细阅读题目,发现你最多能解锁的房间和移动的次数是一样的。这样,每次我们就可以解锁你要走过的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校内训练的主要内容,如果未能解决你的问题,请参考以下文章

校内训练2019-11-15跳一跳

校内训练2019-11-15逮虾户

校内训练2019-11-15表演

20171129校内训练

「csp校内训练 2019-10-30」解题报告

三中校内训练净化