NOIP2013复赛提高组day2(A:积木大赛 B:花匠 C:华容道)

来源:互联网 发布:屏幕录像ios 知乎 编辑:程序博客网 时间:2024/05/16 11:24

这套题还是不错的
基本不需要高端算法
纯靠思维
然而博主这一次是第一次A题爆炸(50分)

A题:
蓝瘦香菇啊!!!!
也不知道当时我是怎么想的,
真是神奇,啧啧~~~
好吧这题其实灰常的简单
一遍O(n)扫过去就好了
这是一道贪心题

如果一块积木左边的积木高度大于等于他,
那么他不用花次数叠
如果他右边的积木高度大于他
右边的积木就要叠和他们的高度差数值相等的次数
于是代码就成型了

#include<bits/stdc++.h>using namespace std;#define M 100005void Rd(int &res){    char c;res=0;    while(c=getchar(),!isdigit(c));    do res=(res<<3)+(res<<1)+(c^48);    while(c=getchar(),isdigit(c));}int main(){    int n,k=0,ans=0;//k即前一块积木的高度    scanf("%d",&n);    for(int i=1;i<=n;i++){        int x;Rd(x);        if(x>k)ans+=x-k;//需要叠        k=x;    }cout<<ans<<endl;    return 0;}

B题:
这题还是可以简单的
不过也同样是一道漂亮的题
我估计这题95%的人都是想到上升和下降两种状态的dp转移
而后用线段树或者是树状数组维护区间最值
博主显然也是蛋总中的一员
然而隔壁的CF大神jiedai写了贪心的O(n)代码
看到他0.3K都不到的带有dp思想的贪心程序,
再回过头来看看自己1.5K冗长的线段树代码
只能说我整个人都不好了
默默感叹一句,CF大神好厉害啊
于是乎这题dp还是好dp的
下降的状态从前面大于自己的上升状态转移过来
上升的状态从前面小于自己的下降状态转移过来
如果我们设上升为1,下降为0的话
状态转移方程如下:
dpi,1=max(dpj,0)+1j0>i1
dpi,0=max(dpj,1)+1j0>i1
于是乎线段树(树状数组)单点更新,区间查询
代码get:

#include<bits/stdc++.h>using namespace std;#define M 100005void Rd(int &res){    char c;res=0;    while(c=getchar(),!isdigit(c));    do res=(res<<3)+(res<<1)+(c^48);    while(c=getchar(),isdigit(c));}int a[M],A[M];int n,m,mx;struct node{    int mx[2];    node(){        mx[0]=0;mx[1]=0;    }}tree[M<<2];void up(int p){    int a=p<<1,b=a+1;    tree[p].mx[0]=tree[a].mx[0];    if(tree[p].mx[0]<tree[b].mx[0])tree[p].mx[0]=tree[b].mx[0];    tree[p].mx[1]=tree[a].mx[1];    if(tree[p].mx[1]<tree[b].mx[1])tree[p].mx[1]=tree[b].mx[1];}int query(int l,int r,int L,int R,int p,int k){//区间查询    if(l>r)return 0;    if(L==l&&R==r)return tree[p].mx[k];    int mid=L+R>>1;    if(r<=mid)return query(l,r,L,mid,p<<1,k);    if(l>mid)return query(l,r,mid+1,R,(p<<1)+1,k);    return max(query(l,mid,L,mid,(p<<1),k),query(mid+1,r,mid+1,R,(p<<1)+1,k));}void update(int L,int R,int x,int v0,int v1,int p){//单点更新    if(L==R){        tree[p].mx[0]=v0;        tree[p].mx[1]=v1;        return;    }int mid=L+R>>1;    if(x<=mid)update(L,mid,x,v0,v1,p<<1);    else update(mid+1,R,x,v0,v1,(p<<1)+1);    up(p);}int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++){        Rd(a[i]);        A[i]=a[i];    }sort(A+1,A+n+1);    m=unique(A+1,A+n+1)-A-1;    for(int i=1;i<=n;i++)a[i]=lower_bound(A+1,A+m+1,a[i])-A;    for(int i=1;i<=n;i++){        int dp0=query(a[i]+1,m,1,n,1,1)+1;        int dp1=query(1,a[i]-1,1,n,1,0)+1;        update(1,n,a[i],dp0,dp1,1);        if(dp0>mx)mx=dp0;//更新答案        if(dp1>mx)mx=dp1;    }printf("%d\n",mx);    return 0;}

而后我们给出隔壁CF大神jiedai的贪心算法
我们知道其实这题是最终所求的是折线的最大条数+1
于是我们很容易可以知道,
对于某一个数而言,若当前的趋势是下降
且他后面的那个数比他小,我们肯定会选取后面那个数
如果比他大的话,趋势就被改变了,折线条数++;
于是乎很容易就能敲出一个贪心算法

#include<stdio.h>int main(){    int n,k,ans=1,f=0;//f表示当前折线的趋势,k表示上一个不等于x的数的大小    scanf("%d%d",&n,&k);    for(int i=1;i<n;i++){        int x,c;        scanf("%d",&x);        if(x!=k){            if(x>k)c=1;            else c=-1;            if(c!=f){f=c;ans++;}//趋势改变            k=x;        }    }printf("%d\n",ans);    return 0;}

C题:
原来这个游戏是华容道。。。。
首先这题70分还是比较容易的
用BFS,
每次把空白棋子移动一步到相邻的可移动棋子上就行了
然而不知为何博主的代码水到了80分。。。
代码。。。:

//PS:这是70分无脑代码#include<bits/stdc++.h>using namespace std;#define M 32int n,m,q;int a[M][M];int rx[]={-1,0,1,0};int ry[]={0,1,0,-1};bool check(int x,int y){    return x>=1&&x<=n&&y>=1&&y<=m;}int mark[M][M][M][M];//以空白棋子,指定棋子的位置定义状态struct node{    int x0,y0,x1,y1,step;}Q[M*M*M*M];//状态int BFS(int x0,int y0,int sx,int sy,int tx,int ty){//BFS随便搞搞就好了    if(sx==tx&&sy==ty)return 0;    memset(mark,0,sizeof(mark));    mark[x0][y0][sx][sy]=1;    node tmp;    tmp.x0=x0;tmp.y0=y0;tmp.x1=sx;tmp.y1=sy;tmp.step=0;    int l=0,r=0;    Q[r++]=tmp;    while(l<r){        tmp=Q[l++];        for(int i=0;i<4;i++){            int x0=tmp.x0+rx[i],y0=tmp.y0+ry[i],x1=tmp.x1,y1=tmp.y1;            if(x1==x0&&y1==y0){                x1=tmp.x0;                y1=tmp.y0;            }if(!check(x0,y0)||!a[x0][y0]||mark[x0][y0][x1][y1])continue;            mark[x0][y0][x1][y1]=1;            if(x1==tx&&y1==ty)return tmp.step+1;            node nxt;            nxt.step=tmp.step+1;            nxt.x0=x0;nxt.y0=y0;nxt.x1=x1;nxt.y1=y1;            Q[r++]=nxt;        }    }return -1;}void solve(){    while(q--){        int x,y,sx,sy,tx,ty;        scanf("%d %d %d %d %d %d",&x,&y,&sx,&sy,&tx,&ty);        printf("%d\n",BFS(x,y,sx,sy,tx,ty));    }}int main(){    scanf("%d %d %d",&n,&m,&q);    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++)            scanf("%d",&a[i][j]);    solve();    return 0;}

好了,AC解法才是我们的正题
我们知道,对于任意一个可移动棋子而言
它若想要移动到某个方向与自己相邻的格子上,
首先空白棋子应该在那个格子上
于是棋子移动一步的距离
就为空白棋子移动到相邻位置的距离+1
(注意:空白棋子移动过程中不能经过该可移动棋子)
但是如果单单这样写的话预处理的东西有点太多了,还是会TLE
于是这个时候我们仔细观察会发现
当可移动棋子移动以后,
空白棋子依旧与它相邻,
于是乎我们只需要预处理出空白棋子在与某一颗移动棋子相邻的点上互相移动的距离就行了
而刚开始空白棋子不一定在可移动棋子旁边,
然并卵
我们这用BFS跑一遍就行了,
当然,还是不能经过可移动棋子。。。
于是我们在预处理某一颗可移动棋子的时候,可以先把它标记成固定棋子
最后跑一遍最短路就可以了
于是这题其实如果想到了思路,
代码就十分简单了
于是乎我们就完美的A掉了这题:

#include<bits/stdc++.h>using namespace std;#define M 32int n,m,q;int a[M][M];int rx[]={-1,0,1,0};int ry[]={0,1,0,-1};int mark[M][M];struct node{    int x,y,step;}Q[M*M];int BFS(int sx,int sy,int tx,int ty){//BFS预处理    if(sx==tx&&sy==ty)return 0;    memset(mark,0,sizeof(mark));    mark[sx][sy]=1;    node tmp;    tmp.x=sx;tmp.y=sy;tmp.step=0;    int l=0,r=0;    Q[r++]=tmp;    while(l<r){        tmp=Q[l++];        for(int i=0;i<4;i++){            int x=tmp.x+rx[i],y=tmp.y+ry[i];            if(!a[x][y]||mark[x][y])continue;            mark[x][y]=1;            if(x==tx&&y==ty)return tmp.step+1;            node nxt;            nxt.step=tmp.step+1;            nxt.x=x;nxt.y=y;            Q[r++]=nxt;        }    }return -1;}int Mark[M][M][4],dis[M][M][4][4];struct NODE{    int x,y,k,step;    bool operator <(const NODE &A)const{        return step>A.step;    }};priority_queue<NODE>QQ;int Dijkstra(int x0,int y0,int sx,int sy,int tx,int ty){//跑最短路lalala    if(sx==tx&&sy==ty)return 0;    //这东西千万不能不加,有35分的数据存在这样的情况,LIN452学姐(大神)就被坑了,只有65分。。    NODE tmp;    tmp.x=sx;tmp.y=sy;    a[sx][sy]=0;    memset(Mark,0,sizeof(Mark));    while(!QQ.empty())QQ.pop();    for(int i=0;i<4;i++){        int x=sx+rx[i],y=sy+ry[i];        if(!a[x][y])continue;        tmp.k=i;        tmp.step=BFS(x0,y0,x,y);        if(tmp.step!=-1)QQ.push(tmp);    }a[sx][sy]=1;    while(!QQ.empty()){        tmp=QQ.top();QQ.pop();        if(Mark[tmp.x][tmp.y][tmp.k])continue;        Mark[tmp.x][tmp.y][tmp.k]=1;        if(tmp.x==tx&&tmp.y==ty)return tmp.step;        for(int i=0;i<4;i++){            if(dis[tmp.x][tmp.y][tmp.k][i]==-1)continue;            NODE nxt;            nxt.step=tmp.step+dis[tmp.x][tmp.y][tmp.k][i]+1;            nxt.x=tmp.x+rx[i];nxt.y=tmp.y+ry[i];            if(!a[tmp.x][tmp.y])continue;            if(i==0)nxt.k=2;if(i==2)nxt.k=0;//移动以后,空白棋子所在的方向会与原来相反            if(i==1)nxt.k=3;if(i==3)nxt.k=1;            QQ.push(nxt);        }    }return -1;}int main(){    scanf("%d %d %d",&n,&m,&q);    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++)            scanf("%d",&a[i][j]);    memset(dis,-1,sizeof(dis));    for(int i=1;i<=n;i++)        for(int j=1;j<=m;j++){            if(!a[i][j])continue;//固定棋子            a[i][j]=0;            for(int k=0;k<4;k++){                int x0=i+rx[k],y0=j+ry[k];                if(!a[x0][y0])continue;//固定棋子                for(int q=0;q<4;q++){                    int x=i+rx[q],y=j+ry[q];                    if(!a[x][y])continue;//固定棋子                    dis[i][j][k][q]=BFS(x0,y0,x,y);//预处理                }            }a[i][j]=1;        }    while(q--){        int x0,y0,sx,sy,tx,ty;        scanf("%d%d%d%d%d%d",&x0,&y0,&sx,&sy,&tx,&ty);        printf("%d\n",Dijkstra(x0,y0,sx,sy,tx,ty));    }return 0;}
3 0
原创粉丝点击