NOIP 2011 题解 铺地毯 选择客栈 Mayan 游戏

11 篇文章 0 订阅
8 篇文章 0 订阅

大家都很强,可与之共勉

大白兔的奶糖’s T 解

T1

1.铺地毯
(carpet.cpp/c/pas)
【问题描述】
为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标
系的第一象限)铺上一些矩形地毯。一共有n 张地毯,编号从1 到n。现在将这些地毯按照
编号从小到大的顺序平行于坐标轴先后铺设,后铺的地毯覆盖在前面已经铺好的地毯之上。
地毯铺设完成后,组织者想知道覆盖地面某个点的最上面的那张地毯的编号。注意:在矩形
地毯边界和四个顶点上的点也算被地毯覆盖。
【输入】
输入文件名为 carpet.in。
输入共 n+2 行。
第一行,一个整数 n,表示总共有n 张地毯。
接下来的 n 行中,第i+1 行表示编号i 的地毯的信息,包含四个正整数a,b,g,k,每
两个整数之间用一个空格隔开,分别表示铺设地毯的左下角的坐标(a,b)以及地毯在x
轴和y 轴方向的长度。
第 n+2 行包含两个正整数x 和y,表示所求的地面的点的坐标(x,y)。
【输出】
输出文件名为 carpet.out。
输出共 1 行,一个整数,表示所求的地毯的编号;若此处没有被地毯覆盖则输出-1。
【输入输出样例 1】
carpet.in
3
1 0 2 3
0 2 3 3
2 1 3 3
2 2
carpet.out
3
【输入输出样例 2】
carpet.in
3
1 0 2 3
0 2 3 3
2 1 3 3
4 5
carpet.out
-1
【数据范围】
对于 30%的数据,有n≤2;
对于 50%的数据,0≤a, b, g, k≤100;
对于 100%的数据,有0≤n≤10,000,0≤a, b, g, k≤100,000。

T解:
这一题乍一看,嘿呀,让我想起了刚刚学信息竞赛的时候的校门外的树用for()来覆盖区间的问题,一定建一个图,然后一看数据范围0≤n≤10,000,0≤a, b, g, k≤100,000。可能是要for()到候年马月了。然而在读题:编号从小到大的顺序平行于坐标轴先后铺设,后铺的地毯覆盖在前面已经铺好的地毯之上。地毯铺设完成后,组织者想知道覆盖地面某个点的最上面的那张地毯的编号。所以既然只要知道那一个点是属于哪一个地毯,我们就不需要把每一点的归属情况找出来,直接特判就好。因为是编号从小到大的顺序铺设,所以for()的时候从n -> 1倒起来找,找到第一个符合的就是答案,输出即可。如果从n -> 1没有找到,就输出无解的情况(-1)。

代码如下:

#include "cstdio"
#include "cctype"

template <class T>
inline bool readIn( T &x )  {
    x = 0;
    T flag = 1;
    char ch = (char) getchar();
    while( !isdigit(ch) )  {
        if( ch == '-' )  flag = -1;
        ch = (char) getchar();
    }
    while( isdigit(ch) )  {
        x = ( x << 1 ) + ( x << 3 ) + ch - 48,
        ch = (char) getchar();
    }
    x *= flag;
}

template <class T>
inline void writeIn( T x )  {
    if( x < 0 )  {
        x = -x;
        putchar('-');
    }
    if( x > 9 )  writeIn( x / 10 );
    putchar(x % 10 + 48);
}

const int MAXN = 10005;
int n, x, y;

struct matrix  {
    int a, b, g, k;
} m[MAXN];

int main()  {
    freopen("carpet.in", "r", stdin);
    freopen("carpet.out", "w", stdout);
    readIn(n);
    for( register int i = 1; i <= n; ++i )  
      readIn(m[i].a),readIn(m[i].b),readIn(m[i].g),readIn(m[i].k);
    readIn(x);readIn(y);
    for( register int i = n; i >= 1; --i )  
        if( x >= m[i].a && x <= m[i].a + m[i].g && y >= m[i].b && y <= m[i].b + m[i].k )  {
            writeIn(i);
            putchar('\n');
            return 0;
        }
    writeIn(-1);
    putchar('\n');
    return 0;
}

T2

2.选择客栈
(hotel.cpp/c/pas)
【问题描述】
丽江河边有 n 家很有特色的客栈,客栈按照其位置顺序从1 到n 编号。每家客栈都按照
某一种色调进行装饰(总共k 种,用整数0 ~ k-1 表示),且每家客栈都设有一家咖啡店,每
家咖啡店均有各自的最低消费。
两位游客一起去丽江旅游,他们喜欢相同的色调,又想尝试两个不同的客栈,因此决定
分别住在色调相同的两家客栈中。晚上,他们打算选择一家咖啡店喝咖啡,要求咖啡店位于
两人住的两家客栈之间(包括他们住的客栈),且咖啡店的最低消费不超过p。
他们想知道总共有多少种选择住宿的方案,保证晚上可以找到一家最低消费不超过p
元的咖啡店小聚。
【输入】
输入文件 hotel.in,共n+1 行。
第一行三个整数 n,k,p,每两个整数之间用一个空格隔开,分别表示客栈的个数,色
调的数目和能接受的最低消费的最高值;
接下来的 n 行,第i+1 行两个整数,之间用一个空格隔开,分别表示i 号客栈的装饰色
调和i 号客栈的咖啡店的最低消费。
【输出】
输出文件名为 hotel.out。
输出只有一行,一个整数,表示可选的住宿方案的总数。
【输入输出样例 1】
hotel.in
5 2 3
0 5
1 3
0 2
1 4
1 5
hotel.out
3
【数据范围】
对于 30%的数据,有n≤100;
对于 50%的数据,有n≤1,000;
对于 100%的数据,有2≤n≤200,000,0<=k≤50,0≤p≤100, 0≤最低消费≤100。

我不会告诉你我最开始写了一个线段树,就是在扯淡嘛我去,要查询多少次。说不定还要TLE。回头一想,这道题可以用dfs(),不过看数据范围dfs()可能会TLE一个点。再仔细分析,其实其实这道题满足最优子结构,而且没有后效性。所以就是一道DP。我想转移方程想了近半个小时,其实不困难。主要是不好理解,而且辅助的数组较多。用col[i]表示颜色为i的客栈之前所有的颜色为i的个数(不包括该客栈),maxc[i]表示之前的颜色为i且编号最大的那一个客栈的编号。f[i]表示1-i中最大的最低消费不大于p的客栈的编号。g[i]表示i之前的色调与i号客栈相同的最大的客栈的编号。same[i]表示i之前与i的色调相同客栈的总数(其实可以不要的,可以直接用col[color],不过方便理解),sol[i]表示色调与i相同而且存在最低消费<=p的数目。(这里面的最大其实就是距离i号客栈最近的意思)。
所以转移方程有1mol多。
每次输入color和least;
g[i] = maxc[color];
if(least<= p) f[i] = i; else f[i] = f[i-1];
if( f[i] < g[i] ) sol[i] = sol[g[i]];
else sol[i] = col[color];
maxcol[color] = i;
++col[color];
主要应该解释if( f[i] < g[i] ) sol[i] = sol[g[i]]; else sol[i] = col[color],我觉得,自己想好了,是想得清楚的。

#include "cstdio"
#include "cctype"

template <class T>
inline bool readIn( T &x )  {
    x = 0;
    T flag = 1;
    char ch = (char) getchar();
    while( !isdigit(ch) )  {
        if( ch == '-' )  flag = -1;
        ch = (char) getchar();
    }
    while( isdigit(ch) )  {
        x = ( x << 1 ) + ( x << 3 ) + ch - 48,
        ch = (char) getchar();
    }
    x *= flag;
}

template <class T>
inline void writeIn( T x )  {
    if( x < 0 )  {
        x = -x;
        putchar('-');
    }
    if( x > 9 )  writeIn( x / 10 );
    putchar(x % 10 + 48);
}

const int MAXN = 200005;
int n, k, p, ans, color, leaco;;
int col[MAXN], f[MAXN], g[MAXN], sol[MAXN], maxcol[MAXN];

int main()  {
    freopen("hotel.in", "r", stdin);
    freopen("hotel.out", "w", stdout);
    readIn(n);readIn(k);readIn(p);
    for( register int i = 1; i <= n; ++i )  {
        readIn(color);readIn(leaco);
        g[i] = maxcol[color];
        if(leaco <= p)  f[i] = i;
        else f[i] = f[i-1];
        if( f[i] < g[i] )  sol[i] = sol[g[i]];
        else  sol[i] = col[color];
        maxcol[color] = i;
        ++col[color];
    }
    for( register int i = 2; i <= n; ++i )  ans += sol[i];
    writeIn(ans);
    putchar('\n');
    return 0;
}

T3

3.Mayan 游戏
(mayan.cpp/c/pas)
【问题描述】
Mayan puzzle 是最近流行起来的一个游戏。游戏界面是一个7 行5 列的棋盘,上面堆放
着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上。游
戏通关是指在规定的步数内消除所有的方块,消除方块的规则如下:
1、每步移动可以且仅可以沿横向(即向左或向右)拖动某一方块一格:当拖动这一方
块时,如果拖动后到达的位置(以下称目标位置)也有方块,那么这两个方块将交换位置(参
见输入输出样例说明中的图6 到图7);如果目标位置上没有方块,那么被拖动的方块将从
原来的竖列中抽出,并从目标位置上掉落(直到不悬空,参见下面图1 和图2);

【输入】
输入文件 mayan.in,共6 行。
第一行为一个正整数 n,表示要求游戏通关的步数。
接下来的 5 行,描述7*5 的游戏界面。每行若干个整数,每两个整数之间用一个空格隔
开,每行以一个0 结束,自下向上表示每竖列方块的颜色编号(颜色不多于10 种,从1 开
始顺序编号,相同数字表示相同颜色)。
输入数据保证初始棋盘中没有可以消除的方块。
【输出】
输出文件名为 mayan.out。

如果有解决方案,输出n 行,每行包含3 个整数x,y,g,表示一次移动,每两个整数
之间用一个空格隔开,其中(x,y)表示要移动的方块的坐标,g 表示移动的方向,1 表示
向右移动,-1 表示向左移动。注意:多组解时,按照x 为第一关健字,y 为第二关健字,1
优先于-1,给出一组字典序最小的解。游戏界面左下角的坐标为(0,0)。
如果没有解决方案,输出一行,包含一个整数-1。
【输入输出样例 1】
mayan.in
3
1 0
2 1 0
2 3 4 0
3 1 0
2 4 3 4 0
mayan.out
2 1 1
3 1 1
3 0 1

样例输入的游戏局面如上面第一个图片所示,依次移动的三步是:(2,1)处的方格向
右移动,(3,1)处的方格向右移动,(3,0)处的方格向右移动,最后可以将棋盘上所有方
块消除。
【数据范围】
对于 30%的数据,初始棋盘上的方块都在棋盘的最下面一行;
对于 100%的数据,0 < n≤5。

#include "cstdio"
#include "cctype"
#include "cstdlib"
#include "cstring"

template <class T>
inline bool readIn( T &x )  {
    x = 0;
    T flag = 1;
    char ch = (char) getchar();
    while( !isdigit(ch) )  {
        if( ch == '-' )  flag = -1;
        ch = (char) getchar();
    }
    while( isdigit(ch) )  {
        x = ( x << 1 ) + ( x << 3 ) + ch - 48,
        ch = (char) getchar();
    }
    x *= flag;
}

int n, color[7][9], doit[6][3], cnt[20]; 

bool remove( int tmp[][9] )  {
    bool pos[7][9], flag = false;
    memset(pos, false, sizeof(pos));
    for( int i = 1; i <= 5; ++i )
      for( int j = 1; j <= 7; ++j )
        if( tmp[i][j] )  {
            if( i <= 3 && tmp[i][j] == tmp[i + 1][j] && tmp[i + 1][j] == tmp[i + 2][j] )
                pos[i][j] = pos[i + 1][j] = pos[i + 2][j] = true;
            if( j <= 5 && tmp[i][j] == tmp[i][j + 1] && tmp[i][j + 1] == tmp[i][j + 2])
                pos[i][j] = pos[i][j + 1] = pos[i][j + 2] = true;
        }
    for( int i = 1; i <= 5; ++i )
      for( int j = 1; j <= 7; ++j )
        if(pos[i][j])  tmp[i][j] = 0, flag = 1;
    return flag;
}

inline bool fall( int tmp[][9] )  {
    int k, t;
    for( int i = 1; i <= 5; ++i )  {
        k = 0;
      for( int j = 1; j <= 7; ++j )  {
        t = tmp[i][j], tmp[i][j] = 0;
        if(t)  tmp[i][++k] = t;
      }
    }
}

bool isclear(int tmp[][9])  {
    for( int i = 1; i <= 5; ++i )
      for( int j = 1; j <= 7; ++j )
      if( tmp[i][j] )  return false;
    return true;
}

void dfs( int step, int tmp[][9] )  {
    if( step > n )  {
        if( isclear(tmp) )  {
            for( int i = 1; i <= n; ++i )  {
                if(doit[i][2])  printf("%d %d -1\n", doit[i][0], doit[i][1] - 1);  // i + 1 -> i;
                else  printf("%d %d 1\n", doit[i][0] - 1, doit[i][1] - 1);
            }
            exit(0);
        }
        return;
    }

    int use[7][9];
    memset(use, 0, sizeof(use));
    memset(cnt, 0, sizeof(cnt));
    for( int i = 1; i <= 5; ++i )
      for( int j = 1; j <= 7; ++j )
        ++cnt[tmp[i][j]];
    for( int i = 1; i <= 10; ++i )
      if( cnt[i] == 1 || cnt[i] == 2 )  return;
    for( int i = 1; i < 5; ++i )
      for( int j = 1; j <= 7; ++j )
        if( tmp[i][j] - tmp[i + 1][j] )  {
            //for( int k = 1; k <= 5; ++k )
                //for( int l = 1; l <= 7; ++l )
                   //use[k][l] = tmp[k][l];
            memcpy(use, tmp, sizeof(use));
            doit[step][0] = i; doit[step][1] = j; doit[step][2] = !tmp[i][j];
            use[i][j] ^= use[i + 1][j]; use[i + 1][j] ^= use[i][j]; use[i][j] ^= use[i + 1][j];
            fall(use);
            while(remove(use))  fall(use);
            dfs( step + 1, use);
        } 
}

int main()  {
    freopen("mayan.in", "r", stdin);
    freopen("mayan.out", "w", stdout);
    readIn(n);
    for(int i = 1, j = 0; i <= 5; ++i, j = 0)
        do  readIn(color[i][++j]);  while( color[i][j] );
    dfs(1, color);
    puts("-1\n");
    return 0;
}

当然用memcpy()要快得多;

成长, 历练, 所向披靡

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值