NOIP2011 铺地毯 选择客栈 Mayan游戏

NOIP2011

这里写图片描述
这里写图片描述
第一题
很简单的题目,通过给定的条件可以求出矩形的左下角与右上角的坐标,进而判断是否覆盖。离线操作,顺序更新。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 10010;

int a[N], b[N], x[N], y[N];

int main(){
    freopen("carpet.in", "r", stdin);
    freopen("carpet.out", "w", stdout);
    int n, xx, yy;
    scanf("%d", &n);
    for(int i=1; i<=n; i++){
        scanf("%d%d%d%d", &a[i],&b[i],&x[i],&y[i]);
    }
    scanf("%d%d", &xx, &yy);
    int ans = 0;
    for(int i=1; i<=n; i++){
        if(a[i] <= xx && b[i] <= yy && a[i] + x[i] >= xx && b[i] + y[i] >= yy)
        ans = i;
    }
    if(ans == 0) printf("-1");
    else printf("%d", ans);
    return 0;
}

这里写图片描述
这里写图片描述
第二题
这道题算是一道dp题,不过有许多其他的解决方案,考试时当然选择最快捷的方案,所以我并没有选择dp,如果读者想要用dp做的话这里有dp的题解下面代码展示的方法是直接判断,只不过不是单纯的dfs,由于条件一是相同颜色,所以我们就把相同颜色的放入一个数组当中,利用单个数组进行判断。又把可以做咖啡店的地方标记出来放入一个数组当中,因为是遍输入遍操作,而且输入又是按照序号的,所以数组中都是有序的,通过lower_bound查找合法选择,再加一丢丢的dp思想,靠后的客栈合法,那么它之后的都合法。运气好是O(n),不好就是O(n*n)了,虽然不是最快的方法,但是对付这道题绰绰有余了。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

int f[55][200010];
int h[55];

int main(){
    freopen("hotel.in", "r", stdin);
    freopen("hotel.out", "w", stdout);
    int n, k, p;
    scanf("%d%d%d", &n,&k,&p);
    for(int i=1; i<=n; i++){
        int col, pri;
        scanf("%d%d", &col, &pri);
        f[col][++h[col]] = i;
        if(pri <= p) f[51][++h[51]] = i;
    }
    f[51][h[51]+1] = 900000;
    long long ans = 0;
    for(int i=0; i<k; i++)
     for(int j=1; j<=h[i]; j++){
      int cc = lower_bound(f[51]+1,f[51]+1+h[51],f[i][j]) - f[51];//满足条件的第一个位置
      int dd = lower_bound(f[i]+1,f[i]+1+h[i],f[51][cc]) - f[i];//满足条件的第一个位置
      if(dd == j) dd++;//不同客栈
      ans += (h[i] - dd + 1);
    }
    printf("%I64d", ans);
    return 0;
}

这里写图片描述
这里写图片描述
这里写图片描述
第三题
由于棋盘较小,又只有两个方向的移动选择,经鉴定,可以搜索。不过各种优化是万万少不了的。如果当前状态有一种颜色的数量小于3,那么这种颜色就无法被消除,于是return。如果两个连着的方块颜色是相同的话,不用交换。如果两个非空的方块交换,我们只用考虑左边那个方块右移(按字典序优先)。如果一个方块是空的,它的右边非空,只考虑它右边的方块左移,当枚举到它右边方块的时候也不需要再考虑左移的情况。要注意,下落时清除方块要反复多次,因为清除方块以后,下落形成的新图形可能又出现了能清除的方块。用memcpy保存上一步状态,便于return时还原。drop函数利用了类似映射的方法,降低了时间复杂度。

#include<cstdio>  
#include<cstring>  
#include<cstdlib>  
#include<iostream>  
using namespace std; 

struct ln{  
    int x,y,dir;  
}ans[10];

int mp[10][10];  
int n;  
int sum[12];

bool empty(){  
    for(int i=0; i<5; i++)  
        for(int j=0; j<7; j++)  
            if(mp[i][j]) return false;  
    return true;  
}//判空 

void drop(){  
    int num[10][10];  
    memset(num,-1,sizeof num);  
    for(int x=0; x<5; x++){  
        int h=0;
        for(int y=0; y<7; y++)  
            if(mp[x][y])  
                num[x][h++] = y;  
    }  
    for(int x=0; x<5; x++)  
        for(int y=0; y<7; y++)
            if(num[x][y] == -1) mp[x][y] = 0;
            else mp[x][y] = mp[x][num[x][y]];  
    return;  
}//映射 

bool clear(){  
    bool flag = 0;  
    for(int x=0; x<3; x++) 
        for(int y=0; y<7; y++)  
        if(mp[x][y]){
            int x2;  
            for(x2=x; x2+1<5 && mp[x2+1][y] == mp[x][y]; x2++);  
            if(x2 - x >= 2){
                int p;
                for(p = x; p <= x2; p++){
                    int Up = y, Dn = y;  
                    while(Up+1<7 && mp[p][Up+1] == mp[x][y]) Up++;  
                    while(Dn-1>=0 && mp[p][Dn-1] == mp[x][y]) Dn--;  
                    if(Up - Dn >= 2){
                        int q;
                        for(q = Dn; q <= Up; q++)  
                            mp[p][q] = 0;  
                    }  
                }  
                for(p = x; p <= x2; p++)  
                    mp[p][y] = 0;  
                flag = 1;  
            }  
        }
    for(int x=0; x<5; x++)  
        for(int y=0; y<5; y++)  
        if(mp[x][y]){
            int y2;  
            for(y2 = y; y2+1<7 && mp[x][y2+1] == mp[x][y]; y2++);  
            if(y2 - y >= 2){
                int q;  
                for(q = y; q <= y2; q++){
                    int LL = x, RR = x;  
                    while(LL-1>=0 && mp[LL-1][q] == mp[x][y]) LL--;  
                    while(RR+1<7 && mp[RR+1][q] == mp[x][y]) RR++;  
                    if(RR - LL >= 2){
                        int p;  
                        for(p = LL; p <= RR; p++)  
                            mp[p][q] = 0;  
                    }  
                }  
                for(q = y; q <= y2; q++)  
                    mp[x][q] = 0;  
                flag = 1;  
            }  
        }
    if(flag) return true;  
    else return false;  
}//消除 

void dfs(int step){  
    if(step > n){
        if(empty()){
            for(int i=1; i<=n; i++){
                if(ans[i].dir)  printf("%d %d %d\n",ans[i].x+1,ans[i].y,-1); //dir==1时->x+1左移 
                else  printf("%d %d %d\n",ans[i].x,ans[i].y,1);  
            }  
            exit(0);  
        }  
        return;  
    }
    for(int i=1; i<=10; i++)//优化 
        if(sum[i] != 0&&sum[i] < 3) return;       
    for(int x=0; x<4; x++)  
        for(int y=0; y<7; y++)  
        if(mp[x][y] != mp[x+1][y]){
            ans[step].x = x;  
            ans[step].y = y;  
            ans[step].dir = (!mp[x][y]);//如果当前这一块是空的话,就把它右边那一块左移  
            int test[10][10];  
            memcpy(test,mp,sizeof test);//保存前一个状态 
            swap(mp[x][y],mp[x+1][y]);
            drop();  
            while(clear()) drop();//下移之后还肯能产生新的合法联通块,因此写成循环的形式
            dfs(step+1); 
            ans[step].x = 0;  
            ans[step].y = 0;  
            ans[step].dir = 0;  
            memcpy(mp,test,sizeof mp);//还原 
        }  
}  

int main(){
    freopen("mayan.in", "r", stdin);
    freopen("mayan.out", "w", stdout);
    scanf("%d",&n);  
    for(int i=0; i<5; i++){
        for(int j=0; ; j++){
            scanf("%d",&mp[i][j]);
            sum[mp[i][j]]++;  
            if(mp[i][j] == 0) break;  
        }  
    }  
    dfs(1);  
    printf("-1\n");  
    return 0;  
}

考试总结
三道题,三个小时。这次比较满意的就是前两道题的效果,只用了一个小时,而且保证了正确度,诀窍就是成熟地分析问题,比较并论证各种方法,择优实行。(更重要的是题目不难【白眼】)不过很遗憾的是,尽管时间足够,第三道题还是没有拿到分。一个极大的原因就是搜索这种基础方法不能特别熟练应用。memcpy这种复制数组的方式其实是见过的,但是却没有想到。“访问无效内存”已经犯了多次了。怎么做到既不滥用数组,又不会出现这种错误呢?方法就是要清晰地理解程序的运行过程,精确范围。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值