HGOI7.10集训题解

题解

今天开局状态不行..NOIP2013day2的题..模拟赛输入输出打错了….’w’,‘W’在DEVc++下真的看不出来好嘛…然后果断地爆了个零。

这里写图片描述


第一题——积木大赛(block)

【题目描述】

  • 告诉你一串序列,题目当中就是积木。告诉你可以连续的 [ L . . R ] (包括 L , R )放上1块积木。至少要多少次从全是0放完。

  • 第一眼看到这道题就想到这个不是区间修改和查询么!首选线段树,次选分块! 看我十分钟给你敲完!!!

    这里写图片描述

  • 但想想不太对..第一题怎么会用这么复杂的数据结构呢!自己举了几个小数据发现了规律:

  • 首先要把地基铺好(就是每一块都可以放上最小值,这都是可以一次性做完的。接下来就要去找有多少需要分开做的。
  • 手动枚举发现..在连续递增下是需要不断增加次数的,因为区间在减小而高度在增加的时候不断上升,就需要增加次数。
  • 而连续递减则不需要,因为在连续的区间内递增时已经把这里的做掉了,就不需要增加了。这样子一来就可以线性地解决。复杂程度 O ( n )
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
void fff(){
    freopen("block.in","r",stdin);
    freopen("block.out","w",stdout);
}
const int MAXN=101000;
int n,ans,minn;
int a[MAXN];
int main(){
    fff();
    scanf("%d",&n);
    ans=0,minn=999999;
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        minn=min(minn,a[i]);
    }
    ans=minn;
    for (int i=1;i<=n;i++){
        if(a[i]>minn){
            if(i==1){
                ans+=a[1]-minn;
                continue;
            }
            if(a[i]>a[i-1]) ans+=a[i]-a[i-1];
        }
    }
    cout<<ans;
    return 0;
}
  • 但其实这道题真的可以用线段树去做(handsometothepolice大佬就手打线段树),还有很多玄妙的算法比如差分也可以去做。

第二题——花匠(flower)

【题目描述】

  • 还是给你一段序列,让你去掉一些数,使得保留下来的新序列符合:
  • 在任意偶数位上的比相邻的数都要大
  • 在任意偶数位上的比相邻的数都要小
  • 以上条件符合任意一个就可以了。输出保留的最大序列的长度

  • 这道题最开始贪了一把..其实也不算贪,类似于正解但没处理好..只拿了八十分。

先说我开局的思路。

  • 我们可以看得出,如果把第一个条件的第一位数拿去后就变成了第二个条件。那么我们要尽量让这个变得更加长,就先进行第一种情况的模拟,再看看第一位或者最后一位能不能进行添上一位似的这个序列更长,符合第二个条件。
  • 而对第一个条件的求得,我们就要让偶数位上的数尽量大,奇数位上的数尽量小就可以达到了。再来枚举头和尾就可以获得答案。
  • 但是很不幸啊我打的时候只判断了头没有判断尾orz。八十分草草收场。

这个是正解。

  • 可以通过画图看出,所需要的序列是一个驼峰式的,那么就可以进行两种规律的dp。方程如下:
    if(a[i]>a[i-1]) 
        sta[i][0]=sta[i-1][1]+1;
    else 
        sta[i][0]=sta[i-1][0];

    if(a[i]<a[i-1]) 
        sta[i][1]=sta[i-1][0]+1;
    else 
        sta[i][1]=sta[i-1][1];

突然断电了…只保存到这里……那只好从这里继续打下去。

  • 解释下这个方程:
  • sta是保存第一和第二种情况的方式的长度。
  • 如果当前这个数值比前一个数值要大,那么符合第一种形式,就接着第二种情况的数量下去。
  • 对于第二种情况也是同理的。然后最后比较到n的时候的两种长度的大小,
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
void fff(){
    freopen("flower.in","r",stdin);
    freopen("flower.out","w",stdout);
}
const int MAXN=101000;
int n;
int a[MAXN];
int sta[MAXN][2],ans=0;
bool flag=true;//falg dizeng
int main(){
//  fff();
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    sta[1][0]=sta[1][1]=1;
    for (int i=2;i<=n;i++){
        if(a[i]>a[i-1]) sta[i][0]=sta[i-1][1]+1;
            else sta[i][0]=sta[i-1][0];
        if(a[i]<a[i-1]) sta[i][1]=sta[i-1][0]+1;
            else sta[i][1]=sta[i-1][1];
    }

    cout<<max(sta[n][0],sta[n][1]);
    return 0;
}

第三题——华容道 (puzzle)

接下来的内容可能会引起读者的不适,请自行评判自己的承受能力再选择是否接下去看。


【题目描述】

  • 这道题就和题目一样啊..但有点不同的情况就是这个的方块没有长度…给你一个 n m 的图,其中一部分点是固定的不能移动(墙?)。进行一串查询,给定空格,目标块,目的地。输出最小的步数似的目标块到目的地。

  • 这道题在洛谷上的评级是省选…所以难度可想而知了….看不懂的大家努力吧…
  • 第一次打了一把暴力深搜…但没有处理好记录状态…打崩了只得了十五分…据说可以暴力广搜打到80…但需要超级强大的剪枝…但性价比真的真的很高了…。

  • 正解有两种:

  • 我学习的正解是预处理所读入的地图…通过广搜(一把清流bfs)获得相邻两点之间的最短距离…然后进行最短路的查询。

  • 效率大概是….不可预测…但预处理的效率确实高..再加上图小,spfa的效果很明显。
  • 主要有两个问题:
  • 1、如何保持状态。因为每一次查询的时候有三个点需要记录,dp至少是6维,果断会爆。那我们就用类似hash的方法进行压缩。比如:
tt=bx*120+by*4+i;//这里的i是指方向,我们下面再讲
  • 2、怎么样快速求出距离。由于查询的次数特别多,那么如果每次查询都要进行一次搜索那不是果断就要爆炸了么…那我们就在读入的时候进行一次预处理。将这map转化成一张图。由于一个点会有可能有四个方向进行出入,所以每回都需要进行四个方向进行搜索orz。预处理之后再来进行查询的广搜,获得所需要局部图的边,再根据这些边来spfa获得答案。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
void fff(){
    freopen("puzzle.in","r",stdin);
    freopen("puzzle.out","w",stdout);
}
const int MAXN=40100;
int n,m,q,tot;
int h[MAXN],to[MAXN],nxt[MAXN],w[MAXN],dist[33][33],qx[1010],qy[1010],d[MAXN];
bool map[35][35];
int mvx[4]={-1,1,0,0};
int mvy[4]={0,0,-1,1};
void add(int x,int y,int z){
    to[++tot]=y;
    nxt[tot]=h[x];
    h[x]=tot;
    w[tot]=z;
}

void bfs(int sx,int sy,int bx,int by,int fx){
//表示从s出发到b的路径求得 
    int tail=1,head=1,xx,yy,tx,ty;
    memset(dist,0,sizeof(dist));
    qx[1]=sx,qy[1]=sy,dist[sx][sy]=1;

    while(head<=tail){
        xx=qx[head],yy=qy[head];
        for (int i=0;i<4;i++){
            tx=xx+mvx[i];//获得新拓展 
            ty=yy+mvy[i];

            if(map[tx][ty]&&!dist[tx][ty]&&(tx!=bx||ty!=by)){//1、要图上能走,2、点没有走过 3、不是目标点 
                dist[tx][ty]=dist[xx][yy]+1;//获得新的距离,也可以说是第几步 

                qx[++tail]=tx;//进队 
                qy[tail]=ty;
            }

        }
        ++head;//出队 
    }
    if(fx==4) return;//沙雕防错,如果不是预处理就不要下去了!!!
    //这里开始 建图存边 


    for (int i=0;i<4;i++){//情况1:空格不经过目标点 
        tx=bx+mvx[i],ty=by+mvy[i];
        if((tx!=sx||ty!=sy)&&dist[tx][ty])
            add(bx*120+by*4+fx,bx*120+by*4+i,dist[tx][ty]-1);//前向星存边,需要压缩点,所以将点变成了x*120+y*4+fx 
    }
    add(bx*120+by*4+fx,sx*120+sy*4+fx^1,1);//情况2:空格和目标点进行交换 
}
int inq[MAXN];
void spfa(int bx,int by){
    int tx,ty,tt;
    queue <int> quq;
    for(int i=0;i<=4000;i++) 
        d[i]=1e7,inq[i]=0;

    for (int i=0;i<4;i++){
        tx=bx+mvx[i];
        ty=by+mvy[i];
        tt=bx*120+by*4+i;
        if(dist[tx][ty]){
            d[tt]=dist[tx][ty]-1;
            quq.push(tt);
            inq[tt]=1;
        }
    }
    while (!quq.empty()){
        int x=quq.front();quq.pop();
        inq[x]=0;
        for (int i=h[x];i!=-1;i=nxt[i]){
            if(d[x]+w[i]<d[to[i]]){
                d[to[i]]=d[x]+w[i];
                if(!inq[to[i]])inq[to[i]]=1,quq.push(to[i]);
            }
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&q);
    memset(h,-1,sizeof(h));
    for (int i=1;i<=n;i++){
        for (int j=1;j<=m;j++){
            scanf("%d",&map[i][j]);
        }
    }
    for (int i=1;i<=n;i++){
        for (int j=1;j<=m;j++){
            if(!map[i][j]) continue;
            if(map[i-1][j]) bfs(i-1,j,i,j,0);//这里是相邻的两个点建立边orz 
            if(map[i+1][j]) bfs(i+1,j,i,j,1);
            if(map[i][j-1]) bfs(i,j-1,i,j,2);
            if(map[i][j+1]) bfs(i,j+1,i,j,3);
        }
    }
    while(q--){
        int sx,sy,bx,by,mx,my;
        scanf("%d%d%d%d%d%d",&sx,&sy,&bx,&by,&mx,&my);
        if(bx==mx&&by==my){
            printf("%d\n",0);
            continue;
        }
        bfs(sx,sy,bx,by,4);
        spfa(bx,by);
        int ans=1e7;
        for (int i=0;i<=3;i++){
            ans=min(ans,d[mx*120+my*4+i]);
        }
        if(ans<1e7) printf("%d\n",ans);else printf("%d\n",-1);
    }
}

  • 第二种解法叫做A*算法(启发式搜索),是结合了深搜和广搜的较优算法,根据评估值来优先选择搜索方向。但…我真的不会打…orz…..我真的是个蒟蒻……..
  • 那只好贴个传送门了…K-XZYdalao的题解

猜你喜欢

转载自blog.csdn.net/qq_42037034/article/details/80986640