首页 > 代码库 > NOIP 2010 题解

NOIP 2010 题解

【前言】最近这的很少弄OI了,虽说是暑假。趁要把NOIP2010~2013的题刷完,我做完一份便写一篇题解吧。


【P1774机器翻译】

P1774机器翻译
Accepted
标签:[显示标签]

描述

小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。

这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词的中文含义,如果内存中有,软件就会用它进行翻译;如果内存中没有,软件就会在外存中的词典内查找,查出单词的中文含义然后翻译,并将这个单词和译义放入内存,以备后续的查找和翻译。

假设内存中有M个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过M-1,软件会将新单词存入一个未使用的内存单元;若内存中已存入M个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。

假设一篇英语文章的长度为N个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。

格式

输入格式

输入文件共2行。每行中两个数之间用一个空格隔开。

第一行为两个正整数M和N,代表内存容量和文章的长度。

第二行为N个非负整数,按照文章的顺序,每个数(大小不超过1000)代表一个英文单词。文章中两个单词是同一个单词,当且仅当它们对应的非负整数相同。

输出格式

包含一个整数,为软件需要查词典的次数。

样例1

样例输入1[复制]

3 7
1 2 1 5 4 4 1

样例输出1[复制]

5

限制

每个测试点1s

提示

对于10%的数据有M=1,N≤5。

对于100%的数据有0<=N<=100,0<=M<=1000。

整个查字典过程如下:每行表示一个单词的翻译,冒号前为本次翻译后的内存状况:
空:内存初始状态为空。

1. 1:查找单词1并调入内存。

2. 1 2:查找单词2并调入内存。

3. 1 2:在内存中找到单词1。

4. 1 2 5:查找单词5并调入内存。

5. 2 5 4:查找单词4并调入内存替代单词1。

6. 2 5 4:在内存中找到单词4。

7. 5 4 1:查找单词1并调入内存替代单词2。

共计查了5次词典。

来源

noip2010提高组复赛

【分析】这大概是纯模拟吧。看样子有点想缓存交换,但是直接暴力即可,甚至连map也不用开。

【代码】

#include<cstdio>
#define N 100005
using namespace std;
int now[N],T[N],ans,m,n,h,t,i,x;
int main()
{
  scanf("%d%d",&m,&n);
  h=1;t=0;
  for (i=1;i<=n;i++)
  {
    scanf("%d",&x);
    if (T[x]) continue;ans++;
    if (t<m) {now[++t]=x;T[x]=1;continue;}
    T[now[h++]]=0;T[now[++t]=x]=1;
  }
  printf("%d",ans);
  return 0;
}

【P1775乌龟棋】

P1775乌龟棋
Accepted
标签:[显示标签]

描述

小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。

乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。

乌龟棋中M张爬行卡片,分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1、2、3、4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。

游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。

很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。

现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?

格式

输入格式

输入文件的每行中两个数之间用一个空格隔开。

第1行2个正整数N和M,分别表示棋盘格子数和爬行卡片数。

第2行N个非负整数,a1a2……aN,其中ai表示棋盘第i个格子上的分数。

第3行M个整数,b1b2……bM,表示M张爬行卡片上的数字。

输入数据保证到达终点时刚好用光M张爬行卡片。

输出格式

输出只有1行,1个整数,表示小明最多能得到的分数。

样例1

样例输入1[复制]

9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1

样例输出1[复制]

73

限制

每个测试点1s

提示

小明使用爬行卡片顺序为1,1,3,1,2,得到的分数为6+10+14+8+18+17=73。注意,由于起点是1,所以自动获得第1格的分数6。

对于30%的数据有1≤N≤30,1≤M≤12。

对于50%的数据有1≤N≤120,1≤M≤50,且4种爬行卡片,每种卡片的张数不会超过20。

对于100%的数据有1≤N≤350,1≤M≤120,且4种爬行卡片,每种卡片的张数不会超过40;0≤ai≤100,1≤i≤N;1≤bi≤4,1≤i≤M。

来源

noip2010提高组复赛

【分析】显然是DP。开始觉得有点难以下手,后来发现卡片只有4种,每种也不多。那么我们可以暴力把每种卡片用了几张当做下标来DP。以为四种卡片用了几张知道后,总路程也知道了,所以只要四维即可。效率是40^4。

【代码】

#include<cstdio>
#include<cstring>
#define N 505
#define C 45
using namespace std;
int f[C][C][C][C],ord[N],d[5];
int P,i,j,k,l,n,m,x;
inline void up(int &x,int add)
{
  int now=P;
  if ((now+=add)>n) return;
  if (f[i][j][k][l]+ord[now]>x) x=f[i][j][k][l]+ord[now];
}
int main()
{
  scanf("%d%d",&n,&m);
  for (i=1;i<=n;i++) scanf("%d",&ord[i]);
  for (i=1;i<=m;i++) 
    scanf("%d",&x),d[x]++;
  memset(f,-1,sizeof(f));
  f[0][0][0][0]=ord[1];
  for (i=0;i<=d[1];i++) 
    for (j=0;j<=d[2];j++)
      for (k=0;k<=d[3];k++)
        for (l=0;l<=d[4];l++)
        {
          if (f[i][j][k][l]==-1) continue;P=i+j*2+k*3+l*4+1;
          if (i<d[1]) up(f[i+1][j][k][l],1);
          if (j<d[2]) up(f[i][j+1][k][l],2);
          if (k<d[3]) up(f[i][j][k+1][l],3);
          if (l<d[4]) up(f[i][j][k][l+1],4);
        }
  printf("%d",f[d[1]][d[2]][d[3]][d[4]]);
  return 0;
}

【P1776关押罪犯】

P1776关押罪犯
Accepted
标签:[显示标签]

描述

S城现有两座监狱,一共关押着N名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c的冲突事件。每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S城Z市长那里。公务繁忙的Z市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。在详细考察了N名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使Z市长看到的那个冲突事件的影响力最小?这个最小值是多少?

格式

输入格式

输入文件的每行中两个数之间用一个空格隔开。

第一行为两个正整数N和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。

接下来的M行每行为三个正整数aj,bj,cj,表示aj号和bj号罪犯之间存在仇恨,其怨气值为cj。

数据保证1<=aj<=bj<=N,0<=cj<=1000000000,且每对罪犯组合只出现一次。

输出格式

输出文件共1行,为Z市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0。

样例1

样例输入1[复制]

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

样例输出1[复制]

3512

限制

每个测试点1s

提示

分配方法:市长看到的冲突事件影响力是3512(由2号和3号罪犯引发)。其他任何分法都不会比这个分法更优。

对于30%的数据有N≤15。

对于70%的数据有N≤2000,M≤50000。

对于100%的数据有N≤20000,M≤100000。

来源

noip2010提高组复赛


【分析】开始看着标签是二分图,心下有点紧张:提高组考这个?结果看完题目觉得是水水的并查集。

有点想食物链?

先是贪心思想。我们按争吵值从大到小排序,每次显然要当前的尽量符合,若不符合就直接输答案。

维护的话用并查集。可以拆点,也可以不拆点用dis来维护到父亲的距离(0或1)。

【代码】

#include<cstdio>
#include<algorithm>
#define N 200005
using namespace std;
struct prisoner
{
  int x,y,z;
  friend inline int operator < (const prisoner &a,const prisoner &b){return a.z>b.z;}
}a[N];
int f[N],d[N],n,m,i,x,y,u,v;
inline int get(int u)
{
  if (f[u]==u) return u;
  int fa=get(f[u]);
  (d[u]+=d[f[u]])&=1;
  return f[u]=fa;
}
int main()
{
  scanf("%d%d",&n,&m);
  for (i=1;i<=m;i++)
    scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
  sort(a+1,a+m+1);
  for (i=1;i<=n;i++) f[i]=i;
  for (i=1;i<=m;i++)
  {
    x=a[i].x;y=a[i].y;
    u=get(x);v=get(y);
    if (u!=v) f[v]=u,d[v]=(d[x]==d[y]);
    else if (d[x]==d[y]) {printf("%d",a[i].z);return 0;}
  }
  puts("0");
  return 0;
}


【P1777引水入城】

P1777引水入城
Accepted
标签:[显示标签]

描述

在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个N行M列的矩形,其中每个格子都代表一座城市,每座城市都有一个海拔高度。

为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。因此,只有与湖泊毗邻的第1行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。

由于第N行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。

格式

输入格式

输入文件的每行中两个数之间用一个空格隔开。

输入的第一行是两个正整数N和M,表示矩形的规模。

接下来N行,每行M个正整数,依次代表每座城市的海拔高度。

输出格式

输出有两行。如果能满足要求,输出的第一行是整数1,第二行是一个整数,代表最少建造几个蓄水厂;如果不能满足要求,输出的第一行是整数0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。

样例1

样例输入1[复制]

2 5
9 1 5 4 3
8 7 6 1 2

样例输出1[复制]

1
1

限制

每个测试点1s

提示

本题共有10个测试数据,每个数据的范围如下表所示:
测试数据编号 能否满足要求 N M
1 不能 ≤ 10 ≤ 10
2 不能 ≤ 100 ≤ 100
3 不能 ≤ 500 ≤ 500
4 能 = 1 ≤ 10
5 能 ≤ 10 ≤ 10
6 能 ≤ 100 ≤ 20
7 能 ≤ 100 ≤ 50
8 能 ≤ 100 ≤ 100
9 能 ≤ 200 ≤ 200
10 能 ≤ 500 ≤ 500
对于所有的10个数据,每座城市的海拔高度都不超过10^6。

来源

noip2010提高组复赛


【分析】这道题有点神啊。后来一想也是可做的。

对于不能,直接把第一排全部当成蓄水池,然后DFS或BFS判断最后一排即可。

对于能,有点棘手。首先有一个结论:第一排的某个点如果装了蓄水池,它影响的最后一排的点一定是连续的。(证明见下)那么我们先用N^2的效率求出第一排的每个点影响的区域L[i]和R[i]。至于求法,我是用记忆化搜索的。之后可以用DP或者贪心,问题转化成了:给定M个区间,求覆盖1~M最少需要几个线段。

【简要证明】

设点X影响的范围是(L1,R1)和(L2,R2),且R1<L2,即有一段空出来的(R1+1,R2-1)

因为是能,一定存在一个第一排的点Y是影响R1+1~R2-1这一段的某一个或几个城市。

那么X和Y到下面的路径一定会交叉。既然是交叉,X也能到Y的地方去。

【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 505
using namespace std;
struct Room{int L,R;}g[N][N],H[N];
int x[N*N],y[N*N],f[N][N],a[N][N];
int n,m,i,j,NO,now,ans,last;
const int dx[4]={0,0,1,-1};
const int dy[4]={1,-1,0,0};
inline void Bfs()
{
  int h=0,t=m;
  for (int i=1;i<=m;i++)
    x[i]=1,y[i]=i,f[1][i]=1;
  while (h<t)
  {
    int X=x[++h],Y=y[h];
    for (int i=0;i<4;i++)
    {
      int xx=X+dx[i],yy=Y+dy[i];
      if (xx<1||xx>n||yy<1||yy>m||f[xx][yy]||a[X][Y]<=a[xx][yy]) continue;
      x[++t]=xx;y[t]=yy;f[xx][yy]=1;
    }
  }
  for (int i=1;i<=m;i++) if (!f[n][i]) NO++;
}
Room Fill(int X,int Y)
{
  if (f[X][Y]) return g[X][Y];
  g[X][Y].L=(X==n)?Y:m+1;g[X][Y].R=(X==n)?Y:0;f[X][Y]=1;
  for (int i=0;i<4;i++)
  {
    int xx=X+dx[i],yy=Y+dy[i];
    if (xx<1||xx>n||yy<1||yy>m||a[X][Y]<=a[xx][yy]) continue;
    Room temp=Fill(xx,yy);
    g[X][Y].L=min(g[X][Y].L,temp.L);
    g[X][Y].R=max(g[X][Y].R,temp.R);
  }
  return g[X][Y];
}
inline int cmp(const Room &a,const Room &b)
{
  if (!a.R||!b.R) return a.R;
  return a.L<b.L||a.L==b.L&&a.R>b.R;
}
int main()
{
  scanf("%d%d",&n,&m);
  for (i=1;i<=n;i++)
    for (j=1;j<=m;j++)
      scanf("%d",&a[i][j]);
  Bfs();
  if (!NO) puts("1");
  else {puts("0");printf("%d",NO);return 0;}
  memset(f,0,sizeof(f));
  for (now=1;now<=m;now++)
    H[now]=Fill(1,now);
  sort(H+1,H+m+1,cmp);
  ans=0;now=0;last=1;
  for (i=1;i<=m&&H[i].R&&last<m;i++)
  {
    if (H[i].L<=last+1) {now=max(now,H[i].R);continue;}
    last=now;now=H[i].R;ans++;
  }
  if (last!=m) ans++;
  printf("%d",ans);
  return 0;
}