NOIP2013提高组Day2

一、积木大赛

数据:对于70%,n∈[1,1000]
对于 100%,n∈[1,100000]

暴力就是从一个点不断往后刷。贪心的想法,每次都要刷较多的区间,那么注意到高的都比相邻的低的刷的次数多,只要记录A[i]-A[i-1]就行了(A[i]>A[i-1])

二、花匠

数据:对于 70%,n∈[1,1000],hi∈[1,1000]
对于 100%,n∈[1,1e5],hi∈[1,1e6]

注意一点:序列要么全部满足A,要么全部满足B

可以看作为一个LIS的变种(波浪LIS),只是判断的东西不同,因为这个判断分两种,所以多定义一维:dp[0][i]表示以i结尾满足A的个数,dp[1][i]表示以i结尾满足B的个数

优化用Bits就行了,和LIS是一样的,往前找,AB两个条件是相互对立的,只是开始是先大还是先小的区别,开始是大,那么就是B,开始是小,那么就是A, 那么其实两者有很大的共同点,可以两者结合考虑:
dp[0]是下一个dp[1]的值,dp[1]是下一个dp[0]的值
所以更新的时候A用B更新,B用A更新 此时可以重新定义dp:
dp[0][i]表示以i结尾,此时这个数大于上个数
dp[1][i]表示以i结尾,此时这个数小于上个数
小技巧:为了简化Bit,找大的时候要用Mx减一下,这样就等于是在找小的
还要注意一点:bit要从1开始,但是题目里说花的高度从0开始,这样就有问题了,那么先把所有花的高度都加1就行了。

Code:

#include<bits/stdc++.h>
#define M 100005
using namespace std;
int n,A[M],dp[2][M],Mx;
struct BITS{//Bit优化段,和LIS是一样的
    int num[1000005];
    int Query(int x){
        int res=0;
        while(x){
            if(num[x]>res)res=num[x];
            x-=x&-x;
        }
        return res;
    }
    void Change(int x,int y){
        while(x<=Mx){
            if(num[x]<y)num[x]=y;
            x+=x&-x;
        }
    }
}bit[2];//因为分奇偶,用两个来装
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&A[i]);A[i]++;
        if(Mx<A[i])Mx=A[i];
    }Mx++;//Mx是用在Bit里的
    int ans=0;
    for(int i=1;i<=n;i++){//A->B,B->A
        dp[0][i]=bit[0].Query(A[i]-1)+1;
        dp[1][i]=bit[1].Query(Mx-A[i]-1)+1;//小技巧
        bit[0].Change(A[i],dp[1][i]);
        bit[1].Change(Mx-A[i],dp[0][i]);//小技巧
        if(ans<dp[0][i])ans=dp[0][i];
        if(ans<dp[1][i])ans=dp[1][i];
    }
    printf("%d\n",ans);
    return 0;
}

三、华容道

数据:对于60%,n,m∈[1,30],q∈[1,10]
对于100%,n,m∈[1,30],q∈[1,500]

考试的时候只想了BFS,显然只有60分(其实有70,只是起点终点一样没判断,少了10分)

正解也要用到BFS,但只是辅助的,主要是一个想法。

注意到如果一个格子要移动,那么就要让空白格子移到这个格子旁边才行。那么可以先让空白格子移到目标格子旁边四个位子(四个位子就是目标格子上下左右四个,空白格位置不同,最终结果也不同)。然后呢?当然是交换空白和目标喽。然后呢?又要把白色格子移到目标格子旁边(虽然现在就在目标格子旁边,但是显然再交换就回到原样了)。那么就是回到了原来的问题上了。既然是同一个问题,加快其速度就很好办了。

因为n,m只有30,那么就先预处理一下。D[i][j][k][l] 表示i,j坐标上的可以行走的格子从k方向到l方向要移动多少步。这个怎么处理?一个一个算吗?当然,反正数据很小,这样算复杂度也就只有n*m*n*m*4(极限复杂度)。

这里写图片描述

然后再对应每组数据,先把空白位置交换到目标点旁边,用BFS,因为只用到旁边就行了,复杂度也只是n*m。最后,就只要跑一遍最短路了。

最短路?为什么是最短路?因为已经知道了各种路径了(抽象的理解一下就行了),让交换次数最小就是路径最小。用SPFA或DJ都行(个人喜欢SPFA,方便,而且这道题不卡)

程序之前理一下过程(程序太难看了,看不懂就尬了)
1、先对所有空白点预处理,处理处一个数组D[i][j][k][l] ,为后面的最短路造边。
2、对于每一组数据,先把空格位置走到目标位置附近,BFS走。
3、最短路跑一遍

Code:

#include<bits/stdc++.h>
using namespace std;
int n,m,cas,kx,ky,sx,sy,ex,ey;
int dx[]={1,0,-1,0};
int dy[]={0,1,0,-1};
int G[35][35],D[35][35][5][5],dis[35][35];
bool mark[35][35];
struct node{int x,y,step;};
queue<node>Q;
void f1(int Sx,int Sy){//被共用的BFS(预处理和每组数据的处理都要)
    memset(dis,63,sizeof dis);
    memset(mark,0,sizeof mark);
    dis[Sx][Sy]=0;mark[Sx][Sy]=1;
    Q.push((node){Sx,Sy,0});
    while(!Q.empty()){
        node now=Q.front();Q.pop();
        for(int i=0;i<4;i++){
            int X=now.x+dx[i],Y=now.y+dy[i];
            if(X<1||X>n||Y<1||Y>m||mark[X][Y]||!G[X][Y])continue;
            mark[X][Y]=1;dis[X][Y]=now.step+1;
            node nxt=now;nxt.x=X;nxt.y=Y;nxt.step++;
            Q.push(nxt);
        }
    }
}
void solve(){//预处理
    memset(D,63,sizeof D);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(G[i][j]){//仅对所有可以移动的位置
                G[i][j]=0;//移动不能穿过自己
                for(int k=0;k<4;k++){
                    if(i+dx[k]<1||i+dx[k]>n||j+dy[k]<1||j+dy[k]>m||!G[i+dx[k]][j+dy[k]])continue;
                    //超出边界或者是墙
                    f1(i+dx[k],j+dy[k]);
                    for(int l=0;l<4;l++)D[i][j][k][l]=dis[i+dx[l]][j+dy[l]];
                }
                G[i][j]=1;
            }
}
struct node1{int x,y,k;};
queue<node1>q;
int dis1[35][35][5],Q1[35][35][5];
void Spfa(){//最短路
    memset(dis1,63,sizeof dis1);
    memset(Q1,0,sizeof Q1);
    while(!q.empty())q.pop();
    for(int i=0;i<4;i++){//因为有四个情况(上下左右),把符合的都放到队列里
        int X=sx+dx[i],Y=sy+dy[i];
        if(X>0&&X<=n&&Y>0&&Y<=m&&G[X][Y]&&dis[X][Y]<1e9){
        //超边界和走不到
            q.push((node1){sx,sy,i});
            Q1[sx][sy][i]=1;
            dis1[sx][sy][i]=dis[X][Y];
        }
    }
    while(!q.empty()){
        node1 now=q.front();q.pop();Q1[now.x][now.y][now.k]=0;
        //这一块是空格和目标位置交换位置
        int X1=now.x+dx[now.k],Y1=now.y+dy[now.k];
        if(dis1[X1][Y1][(now.k+2)%4]>dis1[now.x][now.y][now.k]+1){
            dis1[X1][Y1][(now.k+2)%4]=dis1[now.x][now.y][now.k]+1;
            if(!Q1[X1][Y1][(now.k+2)%4])Q1[X1][Y1][(now.k+2)%4]=1,q.push((node1){X1,Y1,(now.k+2)%4});
        }
        //这一个是空格换到目标格子的旁边(注意判断方向,避免空格位换到空格位)
        for(int i=0;i<4;i++){
            int X=now.x+dx[i],Y=now.y+dy[i];
            if(X<0||X>n||Y<0||Y>m||!G[X][Y]||D[now.x][now.y][now.k][i]>1e9||i==now.k)continue;
            if(dis1[now.x][now.y][i]>dis1[now.x][now.y][now.k]+D[now.x][now.y][now.k][i]){
                dis1[now.x][now.y][i]=dis1[now.x][now.y][now.k]+D[now.x][now.y][now.k][i];
                if(!Q1[now.x][now.y][i])Q1[now.x][now.y][i]=1,q.push((node1){now.x,now.y,i});
            }
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&cas);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)scanf("%d",&G[i][j]);
    solve();
    while(cas--){
        scanf("%d%d%d%d%d%d",&kx,&ky,&sx,&sy,&ex,&ey);
        if(sx==ex&&sy==ey){printf("0\n");continue;}
        G[sx][sy]=0;//同样空格不能穿过目标格
        f1(kx,ky);
        G[sx][sy]=1;
        Spfa();
        int ans=1e9;
        for(int i=0;i<4;i++)ans=min(ans,dis1[ex][ey][i]);//如果是1e9那就不能到达
        if(ans==1e9)printf("-1\n");else printf("%d\n",ans);
    }
    return 0;
}

其实BFS是很好打的,主要是想法不容易想到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值