NOIP2011提高组day1解题报告【2017.3.24更新】

同学大神写的题解

更新日志

  • 2017-3-15补充了第三题的题解
  • 2017-3-18修正了第二题AC代码的一个致命错误
  • 2017-3-18更新了页面排版
  • 2017-3-24更新了更新日志 的排版

第一题

首先来看题目:

为了准备一个独特的颁奖典礼,组织者在会场的一片 矩形区域(可看做 是平面直角坐标系的第一象限)铺上一些矩形地毯。一共有n 张地毯,编 号从1 到n。现在将这些地毯按照编号从小到大的顺序平行于坐标轴先后 铺设,后铺的地毯覆盖在前面已经铺好的地毯之上。地毯铺设完成后,组 织者想知道覆盖地面某个点的最上面的那张地毯的编号。注意:在矩形地 毯边界和四个顶点上的点也算被地毯覆盖。

输入描述 Input Description
输入共 n+2 行。
第一行,一个整数 n,表示总共有n 张地毯。 接下来的 n 行中,第i+1 行表示编号i 的地毯的信息,包含四个正整数 a,b,g,k,每两个整数之间用一个空格隔开,分别表示铺设地毯的左下 角的坐标(a,b)以及地毯在x轴和y 轴方向的长度。 第 n+2 行包含两个正整数x 和y,表示所求的地面的点的坐标(x,y)。
输出描述 Output Description
输出共 1 行,一个整数,表示所求的地毯的编号;若此处没有被地毯覆盖 则输出-1。
样例输入 Sample Input
样例1:
3
1 0 2 3
0 2 3 3
2 1 3 3
2 2

样例2:
3
1 0 2 3
0 2 3 3
2 1 3 3
4 5

样例输出 Sample Output
样例1:
3

样例2:
-1

数据范围及提示 Data Size & Hint
数据范围
对于 30%的数据,有n≤2;
对于 50%的数据,0≤a, b, g, k≤100;
对于 100%的数据,有0≤n≤10,000,0≤a, b, g, k≤100,000。

一道很水的题,刚开始的时候想模拟地毯的铺放来解决,RE掉四个点,后来 发现根本不需要这么做,直接判断就好了,这里两种代码都附上。

//这是模拟的代码,因为用了二维数组,空间开不了多大,会提示“too large”
//开小了又会RE,这里只是用来辅助大家理解题意。
#include <cstdio>
#include <iostream>
using namespace std;

int n, a, b, g, k, x, y;
int map[7010][7010];

template<class T>inline void read(T &res)
{
    static char ch;
    while( (ch=getchar()) < '0' || ch > '9');
        res = ch - 48;
    while( (ch = getchar() ) >= '0' && ch <= '9')
        res = ch - 48 + res * 10;
}

int main()
{
    freopen("carpet.in", "r", stdin);
    freopen("carpet.out", "w", stdout);
    read(n);
    for (int i = 1; i <= n; i++)
    {
        read(a);read(b);read(g);read(k);
        for (int l = b; l <= b+k; l++)
            for (int j = a; j <= a+g; j++)
                map[l][j] = i;
    }
    read(x); read(y);
    if (map[y][x] != 0) cout << map[y][x];
    else cout << -1;
    return 0;
}
//第二种直接判断的代码,时空复杂度都相当优。 
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
using namespace std;

int i, j, k, n, x, y;
int ans[10005][5] = {0};

template<class T>inline void read(T &res)
{
    static char ch;
    while( (ch=getchar()) < '0' || ch > '9');
        res = ch - 48;
    while( (ch = getchar() ) >= '0' && ch <= '9')
        res = ch - 48 + res * 10;
}

int main()
{
    read(n);
    for(i = 1; i <= n; i++)
        for(j = 1; j <= 4; j++)
            read(ans[i][j]);
    read(x);read(y);
    for(i = n; i >= 1; i--)
        if( ( (ans[i][1] + ans[i][3] ) >= x) && ( (ans[i][2] + ans[i][4] ) >= y ) && ans[i][1] <= x && ans[i][2] <= y )
        {
            cout << i;
            return 0;
        }
    cout << -1;
    return 0;
}

 以下是评测截图。

第二题

依旧是先上题目

丽江河边有 n 家很有特色的客栈,客栈按照其位置顺序从1 到n编号。 每家客栈都按照某一种色调进行装饰(总共k 种,用整数0~k-1表示) 且每家客栈都设有一家咖啡店,每家咖啡店均有各自的最低消费。     两位游客一起去丽江旅游,他们喜欢相同的色调,又想尝试两个不同的客栈 因此决定分别住在色调相同的两家客栈中。晚上,他们打算选择一家咖啡店 喝咖啡,要求咖啡店位于两人住的两家客栈之间(包括他们住的客栈)且 咖啡店的最低消费不超过p。     他们想知道总共有多少种选择住宿的方案,保证晚上可以找到一家最低消费 不超过p元的咖啡店小聚。

输入描述 Input Description
     共n+1 行。
     第一行三个整数 n,k,p,每两个整数之间用一个空格隔开,分别表示客栈 的个数,色调的数目和能接受的最低消费的最高值;接下来的 n 行,第i+1 行两个整数,之间用一个空格隔开,分别表示i 号客栈的装饰色调和i 号客 栈的咖啡店的最低消费

输出描述 Output Description
     输出只有一行,一个整数,表示可选的住宿方案的总数。

样例输入 Sample Input
5 2 3
0 5
1 3
0 2
1 4
1 5

样例输出 Sample Output
3

数据范围
对于 30%的数据,有n≤100;
对于 50%的数据,有n≤1,000;
对于 100%的数据,有2≤n≤200,000,0<k≤50,0≤p≤100, 0≤最低消费≤100。

先分析题目,很明显可以暴力(手动滑稽),暴力的思路就是直接枚举,从第一家客栈开始找,找到颜色相符合的就去枚举价格,如果价格符合要求(≤p)就ans++,不过很明显,会T掉。

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

int n, k, p, ans;
int t = 1;
int color[200010];
int house[200010];

template<class T>inline void read(T &res)
{
    static char ch;
    while( (ch=getchar()) < '0' || ch > '9');
        res = ch - 48;
    while( (ch = getchar() ) >= '0' && ch <= '9')
        res = ch - 48 + res * 10;
}

int DFS()
{
    int mi;
    for (register int i = 1; i <= n; i++)
    {
        for (register int j = t+1; j <= n; j++)
        {
            if (color[j] == color[t])
            {
                mi = 233333333;
                for (int e = t; e <= j; e++)
                {
                    if (house[e] < mi)
                    {
                        mi = house[e];
                    }
                }
                if (mi <= p) ans++;
            }
        }
        t++;
    }
    return ans;
}

int main()
{
    read(n);read(k);read(p);
    for (register int i = 1; i <= n; i++)
    {
        read(color[i]);
        read(house[i]);
    }
    cout << DFS();
    return 0;
}

可以很清晰的看见这个做法有多么简单粗暴,但是就算是加了register一样会超时,超四个点,那么正解是什么呢?

如果一家编号为i咖啡店的最低消费大于p,则记flag[i]=1否则,flag[i]=0但计算满足要求的对数有点恼火,从反面思考:从1到n扫描出flag值等于1的连续客栈(在此区间内找到的客栈均为不满足要求的方案),在检测时用cnt[j]记录当前颜色为j的客栈出现次数cnt[j]++;count+=(cnt [j]-1); (新的客栈可以与当前任意颜色相同客栈组合)可以数出颜色相同但不满足要求的客栈对数。答案ans=sum-count.(sum为所有颜色相同的客栈对数)即为满足要求的客栈对数。

直接贴代码。

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

int Count, sum, n, k, p, tmp;
int cnt[100];
int col[200010];
bool flag[200010];

int main()
{
    scanf("%d%d%d", &n, &k, &p);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &col[i]);
        scanf("%d", &tmp);
        if(tmp > p) flag[i] = 1;
    }
    for(int i = 1; i <= n; i++)
    {
        if(flag[i] == 0)
        {
            memset(cnt, 0, sizeof(cnt));
            continue;
        }
        cnt[col[i]]++;
        Count += (cnt[col[i]] - 1);
    }
    memset(cnt, 0, sizeof(cnt));
    for(int i = 1; i <= n; i++)
    {
        cnt[col[i]]++;
        sum += (cnt[col[i]] - 1);
    }
    cout << sum-Count;
    return 0;
}

第三题

Top↑

Mayan puzzle 是最近流行起来的一个游戏。 游戏界面是一个7 行5 列的棋盘,上面堆放 着一些方块,方 块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块 之上。游 戏通关是指在规定的步数内消除所有的方块,消除方块 的规则如下:

1、每步移动可以且仅可以沿横向(即向左或向右)拖动某一方块 一格:当拖动这一方 块时,如果拖动后到达的位置(以下称目标 位置)也有方块,那么这两个方块将交换位置(参 见输入输出样 例说明中的图6 到图7);如果目标位置上没有方块,那么被拖动 的方块将从 原来的竖列中抽出,并从目标位置上掉落

2、任一时刻,如果在一横行或者竖列上有连续三个或者三个以上 相同颜色的方块,则 它们将立即被消除(参见图1 到图3)。注意:
a) 如果同时有多组方块满足消除条件,几组方块会同时被消除( 例如下面图4,三个颜 色为1 的方块和三个颜色为2 的方块会同 时被消除,最后剩下一个颜色为2 的方块)。
b) 当出现行和列都满足消除条件且行列共享某个方块时,行和列 上满足消除条件的所 有方块会被同时消除(例如下面图5 所示的 情形,5 个方块会同时被消除)。

3、方块消除之后,消除位置之上的方块将掉落,掉落后可能会引 起新的方块消除。注 意:掉落的过程中将不会有方块的消除。 上 面图 1 到图3 给出了在棋盘上移动一块方块之后棋盘的变化。棋 盘的左下角方块的坐 标为(0, 0),将位于(3, 3)的方块向左 移动之后,游戏界面从图1 变成图2 所示的状态, 此时在一竖列上 有连续三块颜色为4 的方块,满足消除条件,消除连续3 块颜色为4 的方块 后,上方的颜色为3 的方块掉落,形成图3 所示的局面。

【输入描述】
输入共六行。
第一行输入一个正整数n,表示要求游戏通关的步数; 接下来五行,描述了7*5的游戏界面,每行输入若干个整数,每行以一 个0 结束,自下向上表示每竖列方块的颜色编号(颜色不多于10种,从 1开始顺序编号,相同数字表示相同颜色)。 保证初始棋盘中没有可以消除的方块。

【输出描述】
如果有解决方案,输出n行,每行包含三个整数x、y、g,表示一次移 动,其中(x,y)表示要移动的方块的坐标,g表示移动的方向,1表示 向右移动,-1表示向左移动。当存在多组解时,按照x为第一关健字, y为第二关健字,1优先于-1,输出一组字典序最小的解。游戏界面左 下角的坐标为(0,0);

如果没有解决方案,输出一个整数-1。

【输入样例】
3
1 0
2 1 0
2 3 4 0
3 1 0
2 4 3 4 0

【输出样例】
2 1 1
3 1 1
3 0 1

 据坊间传闻这是NOIP历年来最难得一道搜索,对个人而言确实还是有难度。

其实主要难在删除块儿和下落,还有具体的优化,下面上代码,代码上都有批注。

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

int n;
int Num[15], i[6][8], Ans[6][3];

bool DEL(int i[6][8]) //清除
{
    bool Flag = 0, f[6][8]={0};
    for (int a = 1; a <= 5; a++)
      for (int b = 1; b <= 7; b++)
        if (i[a][b])
        {
            if (a <= 3 && i[a][b] == i[a+1][b] && i[a+1][b] == i[a+2][b])
              f[a][b] = f[a+1][b] = f[a+2][b] = true;
            if (b <= 5 && i[a][b] == i[a][b+1] && i[a][b+1] == i[a][b+2])
              f[a][b] = f[a][b+1] = f[a][b+2] = true;
        }
    for (int a = 1; a <= 5; a++)
      for (int b = 1; b <= 7; b++)
        if (f[a][b])
        {
            i[a][b] = 0;
            Flag = true;
        }
    return Flag;
}

void Falldown(int i[6][8]) //下落
{
    for (int a = 1; a <= 5; a++) //填补
    {
        int t = 0;
        for (int b = 1; b <= 7; b++)
        {
            int T = i[a][b];
            i[a][b] = 0;
            if (T)
              i[a][++t] = T;
        }
    }
}

bool Check(int i[6][8]) //检验
{
    for (int a = 1; a <= 5; a++)
      for (int b = 1; b <= 7; b++)
        if (i[a][b])
        return false;
    return true;
}

void DFS(int T, int i[6][8]) //DFS
{
    if (T > n) //超步子 
    {
        if (Check(i))
        {
            for (int a = 1; a <= n; a++)
              if (Ans[a][2]) //判断左右
                printf("%d %d -1
", Ans[a][0], Ans[a][1]-1);
              else
                printf("%d %d 1
", Ans[a][0]-1, Ans[a][1]-1);
            exit(0);//结束 
        }
        return;
    }
    memset(Num, 0, sizeof(Num)); //存储某种颜色格子现存数
    for (int a = 1; a <= 5; a++)
      for (int b = 1; b <= 7; b++)
        if (i[a][b])
          Num[i[a][b]]++;
    for (int a = 1; a <= 10; a++)
      if (Num[a] == 1 || Num[a] == 2) //等于0时并不需要返回
        return;
    int f[6][8] = {0}; //临时数组
    for (int a = 1; a < 5; a++)
      for (int b = 1; b <= 7; b++)
        if (i[a][b] != i[a+1][b]) //颜色不一样才交换 
        {
            memcpy(f, i, sizeof(f));//复制值 
            Ans[T][0] = a;
            Ans[T][1] = b;
            Ans[T][2] =! i[a][b]; //若空,即是true,若有,即是false
            swap(f[a][b], f[a+1][b]); //交换
            Falldown(f);
            while (DEL(f)) //消除
              Falldown(f);
            DFS(T+1, f);
        }
}
int main()
{
    scanf("%d", &n);
    for (int a = 1; a <= 5; a++)
      for (int b = 1; ; b++)
      {
        scanf("%d", &i[a][b]); //i列j行 
        if (!i[a][b]) //存储颜色
          break;
      }
    DFS(1, i);
    printf("-1");//执行至此如果没有结束则输出-1 
    return 0;
}
原文地址:https://www.cnblogs.com/GuanHuaEdison/p/6535478.html