NOIP 2011复赛题目总结
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。
总结与分析:最开始我想的是用模拟来实现但我发现数组根本开不下那么多,然后我又仔细读了一遍题目,他说的是输出被覆盖点的最上面那个地毯的编号,那么我们只用反着找一遍,找到的第一个地毯把点给覆盖的就是答案。如何证明其正确性呢?
我们用反证法:假设我从n到1枚举找到的第一个把点覆盖的地毯不是最上一层的,那么必然有一个在这个地毯上的地毯,我们又知道地毯是先后顺序铺的(这很关键)所以在这个地毯之上的那个地毯的编号必然大于找到的地毯,那么就与先前我们从n到1枚举矛盾。
下面是代码
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n, aa[10005][5];
int main() {
freopen("carpet.in","r",stdin);
freopen("carpet.out","w",stdout);
scanf( "%d", &n );
int x, y, x1, y1, p, q;
for ( int i = 1; i <= n; i++ ) {
scanf( "%d%d%d%d", &x, &y, &x1, &y1 );
aa[i][1] = x;
aa[i][2] = y;
aa[i][3] = x + x1;
aa[i][4] = y + y1;
}
scanf( "%d%d", &p, &q );
int i;
for ( i = n; i >= 1; i-- ) {
if ( p >= aa[i][1] && p <= aa[i][3] && q >= aa[i][2] && q <= aa[i][4] )
break;
}
if ( i == 0 ) printf( "-1\n" );
else printf( "%d\n", i );
return 0;
}
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
【输入输出样例说明】
客栈编号 ① ② ③ ④ ⑤
色调 0 1 0 1 1
最低消费 5 3 2 4 5
2 人要住同样色调的客栈,所有可选的住宿方案包括:住客栈①③,②④,②⑤,④⑤,
但是若选择住4、5 号客栈的话,4、5 号客栈之间的咖啡店的最低消费是4,而两人能承受
的最低消费是3 元,所以不满足要求。因此只有前3 种方案可选。
【数据范围】
对于 30%的数据,有n≤100;
对于 50%的数据,有n≤1,000;
对于 100%的数据,有2≤n≤200,000,0 < k≤50,0≤p≤100, 0≤最低消费≤100。
总结与分析:这道题我最开始的想法就是线段树因为我看到n范围比较大比较符合数据结构于是我就应是弄出来个线段树优化的暴力
然后就过了具体看代码
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 200005;
struct node{
node *ls, *rs;
int minn;
node() {
ls = rs = NULL;
}
};
node pool[N * 4], *tail = pool, *root;
int val[N], col[N], n, k, p, deal[N];
node *build( int lf, int rg ) {
node *nd = ++tail;
if ( lf == rg ) {
nd->minn = val[lf];
}
else {
int mid = (lf + rg) >> 1;
nd->ls = build(lf , mid);
nd->rs = build(mid+1, rg);
nd->minn = min(nd->ls->minn,nd->rs->minn);
}
return nd;
}
int query( node *nd, int lf, int rg, int L, int R ) {
int rt = 0x3f3f3f3f;
if ( L <= lf && rg <= R ) {
return nd->minn;
}
else {
int mid = (lf + rg) >> 1;
if ( L <= mid )
rt = min( rt, query( nd->ls, lf, mid, L, R) );
if ( R > mid )
rt = min( rt, query( nd->rs, mid+1, rg, L, R ) );
}
return rt;
}
struct date{
int id[N], tot;
date() {
tot = 0;
}
};
date aa[51];
int main() {
freopen("hotel.in","r",stdin);
freopen("hotel.out","w",stdout);
scanf( "%d%d%d", &n, &k, &p );
int x, y;
for ( int i = 1; i <= n; i++ ) {
scanf( "%d%d", &x, &y );
aa[x].tot++;
aa[x].id[aa[x].tot] = i;
val[i] = y;
}
root = build(1, n);
int haha = 0;
for ( int i = 0; i < k; i++ ) {
memset( deal, 0, sizeof(deal) );
for ( int j = 1; j < aa[i].tot; j++ )
if ( query(root, 1, n, aa[i].id[j], aa[i].id[j+1]) > p ) deal[j] = 1;
int num = 0, ans = aa[i].tot*(aa[i].tot-1)/2;
for ( int j = 1; j < aa[i].tot; j++ ) {
if ( deal[j] == 1 ) num++;
else {
ans -= num*(num+1)/2;
num = 0;
}
}
ans -= num*(num+1)/2;
haha += ans;
}
printf( "%d\n", haha );
return 0;
}
然而这并不是正规军的做法,正规军的做法是O(n)的而且短了很多这里我以我班一OI大牛Lemonoil童鞋的代码为例
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
template<class T>inline void readin(T &res){
static char ch;
while((ch=getchar())<'0'||ch>'9');res=ch-48;
while((ch=getchar())<='9'&&ch>='0')res=res*10+ch-48;
}
bool flag[200010];
int cnt[100],_count,sum,n,k,p,col[200010],tmp;
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(col[i]),readin(tmp);
if(tmp>p)flag[i]=1;
}
for(register 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(register int i=1;i<=n;i++){
cnt[col[i]]++;
sum+=(cnt[col[i]]-1);
}
cout<<sum-_count<<endl;
return 0;
}
用flag把所有不合法的旅馆打标记然后下一个for循环处理出不合法的区间,再下一个for循环处理出所有区间,那么相减便得到了正确的答案。
3.Mayan 游戏
(mayan.cpp/c/pas)
【问题描述】
Mayan puzzle 是最近流行起来的一个游戏。游戏界面是一个7 行5 列的棋盘,上面堆放
着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上。游
戏通关是指在规定的步数内消除所有的方块,消除方块的规则如下:
1、每步移动可以且仅可以沿横向(即向左或向右)拖动某一方块一格:当拖动这一方
块时,如果拖动后到达的位置(以下称目标位置)也有方块,那么这两个方块将交换位置(参
见输入输出样例说明中的图6 到图7);如果目标位置上没有方块,那么被拖动的方块将从
原来的竖列中抽出,并从目标位置上掉落(直到不悬空,参见下面图1 和图2);
2、任一时刻,如果在一横行或者竖列上有连续三个或者三个以上相同颜色的方块,则
它们将立即被消除(参见图1 到图3)。
注意:
a) 如果同时有多组方块满足消除条件,几组方块会同时被消除(例如下面图4,三个颜
色为1 的方块和三个颜色为2 的方块会同时被消除,最后剩下一个颜色为2 的方块)。
b) 当出现行和列都满足消除条件且行列共享某个方块时,行和列上满足消除条件的所
有方块会被同时消除(例如下面图5 所示的情形,5 个方块会同时被消除)。
输入文件 mayan.in,共6 行。
第一行为一个正整数 n,表示要求游戏通关的步数。
接下来的 5 行,描述7*5 的游戏界面。每行若干个整数,每两个整数之间用一个空格隔
开,每行以一个0 结束,自下向上表示每竖列方块的颜色编号(颜色不多于10 种,从1 开
始顺序编号,相同数字表示相同颜色)。
输入数据保证初始棋盘中没有可以消除的方块。
总结与分析:这道题我最开始就想到要DFS深搜大暴力,因为n<=5所以他的搜索树深度不会很深,但它实在太难写,我不是一个合格的码农- -所以我最后直接打了-1骗了20分。。。。。
所以这说明代码能力要增强。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
int cnt[15] ,color[10][10] ,n ,lin[6][3] ;
bool remove(int temp[][10])
{
bool pos[10][10]={0} ,flag=0;
for(int i=1;i<=5;++i)
for(int j=1;j<=7;++j)
if(temp[i][j])
{
if(i<=3&&temp[i][j]==temp[i+1][j]&&temp[i+1][j]==temp[i+2][j])
pos[i][j]=pos[i+1][j]=pos[i+2][j]=1;
if(j<=5&&temp[i][j]==temp[i][j+1]&&temp[i][j+1]==temp[i][j+2])
pos[i][j]=pos[i][j+1]=pos[i][j+2]=1;
}
for(int i=1;i<=5;++i)
for(int j=1;j<=7;++j)
if(pos[i][j])
temp[i][j]=0 ,flag=1 ;
return flag;
}
void falldown(int temp[][10])
{
int k ,tmp ;
for(int i=1;i<=5;++i)
{
k=0;
for(int j=1;j<=7;++j)
{
tmp=temp[i][j] ,temp[i][j]=0 ;
if(tmp)temp[i][++k]=tmp;
}
}
}
bool check(int temp[][10])
{
for(int i=1;i<=5;++i)
for(int j=1;j<=7;++j)
if(temp[i][j])return 0;
return 1;
}
void dfs(int code,int temp[][10])
{
if(code>n)
{
if(check(temp))
{
for(int i=1;i<=n;++i)
{
if(lin[i][2])printf("%d %d -1\n",lin[i][0],lin[i][1]-1);
else printf("%d %d 1\n",lin[i][0]-1,lin[i][1]-1);
}
exit(0);
}
return;
}
int tmp[10][10]={0} ;
memset(cnt,0,sizeof cnt);
for(int i=1;i<=5;++i)
for(int j=1;j<=7;++j)
++cnt[temp[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(temp[i][j]!=temp[i+1][j])
{
memcpy(tmp,temp,sizeof tmp);
lin[code][0]=i ,lin[code][1]=j ,lin[code][2]=!temp[i][j];
swap(tmp[i][j],tmp[i+1][j]);
falldown(tmp);
while(remove(tmp))
falldown(tmp);
dfs(code+1,tmp);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=5;++i)
for(int j=1;;++j)
{
scanf("%d",&color[i][j]);
if(!color[i][j])break;
}
dfs(1,color);
puts("-1");
return 0;
}
总结:这次考试总共得了220分,不算很好,一般了,总共花了3个小时,前面两道题花了我1个小时半,但最后一道题我硬是在那迷了半天,而且我这种暴力编写的能力不强上一次的靶形数独我也没编出来,这就很考验选手考虑到各个dfs的方案,并且想到许多优化来剪枝。