NOIP 2010 解题报告(机器翻译,乌龟棋,关押罪犯,引水入城)

作者:onepointo

1.机器翻译 (translate.pas/c/cpp)
【问题描述】
小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。
这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词的中文含义,如果内存中有,软件就会用它进行翻译;如果内存中没有,软件就会在外存中的词典内查找,查出单词的中文含义然后翻译,并将这 个单词和译义放入内存,以备后续的查找和翻译。
假设内存中有 M 个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过 M-1,软件会将新单词存入一个未使用的内存单元;若内存中已存入 M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。
假设一篇英语文章的长度为 N个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。
【输入】
输入文件名为 translate.in,输入文件共 2 行。每行中两个数之间用一个空格隔开。
第一行为两个正整数 M和 N,代表内存容量和文章的长度。
第二行为 N 个非负整数,按照文章的顺序,每个数(大小不超过 1000)代表一个英文单词。文章中两个单词是同一个单词,当且仅当它们对应的非负整数相同。
【输出】
输出文件 translate.out 共1行,包含一个整数,为软件需要查词典的次数。
【数据范围】
对于 10%的数据有 M=1,N≤5。
对于 100%的数据有 M<=100 ,N<=1000。
【解题报告】
模拟大水题,开个队列维护。
队列就相当于是题中的内存,每输入一个数字就在内存区间里查找一遍,如果已经存在的话就不管,如果没有的话入队。
当然也可以再开一个数组存位置到达查找时O(n)的复杂度,不过从数据范围上来说不需要。。。**

代码如下

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

int n,m,tot=0,t=1,h=0;
int s[500]={0};

int main()
{
    freopen("translate.in","r",stdin);
    freopen("translate.out","w",stdout);
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++)
    {
        int w;
        scanf("%d",&w);
        for(int j=t;j<=h;j++)
        if(s[j]==w) goto here; 
        //如果内存中已有该数,就直接不执行接下来的操作(goto的优越性就体现出来了)
        h++;
        s[h]=w;
        tot++;
        if(h-t==m) t++;
        here:;
    }
    printf("%d\n",tot);
    return 0;
}

2.乌龟棋 (tortoise.pas/c/cpp)
【问题描述】
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行 N个格子,每个格子上一个分数(非负整数)。棋盘第 1 格是唯一
的起点,第 N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。

乌龟棋中 M 张爬行卡片,分成 4 种不同的类型(M 张卡片中不一定包含所有 4 种类型的卡片,见样例),每种类型的卡片上分别标有 1、2、3、4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
【输入】
输入文件名 tortoise.in。输入文件的每行中两个数之间用一个空格隔开。
第 1 行2 个正整数 N和 M,分别表示棋盘格子数和爬行卡片数。
第 2 行 N个非负整数,a1, a2, ……, aN,其中 ai 表示棋盘第 i 个格子上的分数。
第 3 行M 个整数,b1,b2, ……, bM,表示 M 张爬行卡片上的数字。
【输出】
输出文件名 tortoise.out。
输出只有 1行,1 个整数,表示小明最多能得到的分数。
【数据范围】
对于 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。
【结题报告】
一开始看到这道题的数据范围,还以为是裸考搜索,然而并不会这么简单粗暴。
其实是简单的DP,f[l1][l2][l3][l4]表示当选择l1个走1步的牌,l2个走2步的牌,l3个走3步的牌,l4个走4步的牌达到的最大收益。
转移方程也就自然地出来了,写成四个方便加条件判断。
做题的时候数组开小了,结果只有50分,还以为是算法的缺陷。。。**

代码如下

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 45

int f[N][N][N][N],num[5],a[500];
int n,m;

int main()
{
    freopen("tortoise.in","r",stdin);
    freopen("tortoise.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    for(int i=1;i<=m;i++)
    {
        int x;scanf("%d",&x);
        num[x]++;
    }
    f[0][0][0][0]=a[1];
    for(int l1=0;l1<=num[1];l1++)
    for(int l2=0;l2<=num[2];l2++)
    for(int l3=0;l3<=num[3];l3++)
    for(int l4=0;l4<=num[4];l4++)
    {
        int s=l1+l2*2+l3*3+l4*4+1;
        if(l1)
        f[l1][l2][l3][l4]=max(f[l1][l2][l3][l4],f[l1-1][l2][l3][l4]+a[s]);
        if(l2)
        f[l1][l2][l3][l4]=max(f[l1][l2][l3][l4],f[l1][l2-1][l3][l4]+a[s]);
        if(l3)
        f[l1][l2][l3][l4]=max(f[l1][l2][l3][l4],f[l1][l2][l3-1][l4]+a[s]);
        if(l4)
        f[l1][l2][l3][l4]=max(f[l1][l2][l3][l4],f[l1][l2][l3][l4-1]+a[s]);
    }
    printf("%d\n",f[num[1]][num[2]][num[3]][num[4]]);
    return 0;
}

3.关押罪犯 (prison.pas/c/cpp)
【问题描述】
S城现有两座监狱,一共关押着 N名罪犯,编号分别为 1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了 N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
【输入】
输入文件名为 prison.in。输入文件的每行中两个数之间用一个空格隔开。
第一行为两个正整数 N和 M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
接下来的 M行每行为三个正整数 aj,bj,cj,表示 aj号和 bj号罪犯之间存在仇恨,其怨气值为 cj。数据保证1≤aj
【输出】
输出文件prison.out 共1 行,为Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0
【数据范围】
对于 30%的数据有 N≤15。
对于 70%的数据有 N≤2000,M≤50000。
对于 100%的数据有 N≤20000,M≤100000。
【结题报告】
一开始看到题里面给出的图还以为是图论,感觉像是怎么割图的问题,一时间被吓到了,所以直接就输出了零不厚道地骗了十分。
其实可以用并查集来解决。
算法思路其实不难理解,大概就是
先将所有的仇恨值从大到小排个序,然后把每一组分别往两个监狱里面放,
直到出现冲突的那个就是最大值,如果一直没有冲突就输出零。**

代码如下

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

struct edge
{
    int x,y,v;
}cr[100050];
bool cmp(edge a,edge b) {return a.v>b.v;}
int n,m;
int an[20050],pr[20050],en[20050];

int get_father(int x)
{
    if(pr[x]==x) return x;
    pr[x]=get_father(pr[x]);
    return pr[x];
}
void Union(int x,int y)
{
    int fx=get_father(x);
    int fy=get_father(y);
    if(fx!=fy) pr[fx]=fy;
}

int main()
{
    freopen("prison.in","r",stdin);
    freopen("prison.out","w",stdout);
    scanf("%d%d",&n,&m);
    int x,y,v;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&cr[i].x,&cr[i].y,&cr[i].v);
    }
    sort(cr+1,cr+m+1,cmp);
    memset(pr,0,sizeof(pr));
    memset(en,0,sizeof(en));
    for(int i=1;i<=n;i++) {pr[i]=i;en[i]=0;}
    for(int i=1;i<=m;i++)
    {
        x=cr[i].x;
        y=cr[i].y;
        int fx=get_father(x);
        int fy=get_father(y);
        if(fx==fy)
        {
            printf("%d\n",cr[i].v);
            return 0;
        }
        if(en[x]==0) en[x]=y;
        else Union(y,en[x]);
        if(en[y]==0) en[y]=x;
        else Union(x,en[y]);
    }
    printf("0\n");
    return 0;
}

4.引水入城 (flow.pas/c/cpp)
【问题描述】
在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个 N行 M 列的矩形,如上图所示,其中每个格子都代表一座城市,每座城市都有一个海拔高度。
为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。因此,只有与湖泊毗邻的第 1 行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。
由于第 N 行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。
【输入】
输入文件名为 flow.in。输入文件的每行中两个数之间用一个空格隔开。
输入的第一行是两个正整数 N和 M,表示矩形的规模。
接下来 N行,每行 M 个正整数,依次代表每座城市的海拔高度。
【输出】
输出文件名为 flow.out。
输出有两行。如果能满足要求,输出的第一行是整数 1,第二行是一个整数,代表最少建造几个蓄水厂;如果不能满足要求,输出的第一行是整数 0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。
【解题报告】
一开始以为是DP+贪心什么的,没什么思路,所以特判了第四个n==1的点,又不厚道地骗了十分。
其实可以证明以某城市为蓄水池,能够覆盖到的沙漠城市一定是连续的。
这样就可以用BFS先得到以每一个海滨城市蓄水能覆盖到的沙漠城市的区域(DFS会暴栈),
然后DP解决区间覆盖问题就行了。**

代码如下

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

struct Edge
{
    int L,R;
}B[502];
int n,m,ans;
int F[502];
int map[502][502];
int vis[502][502]={0};
int Dx[4]={-1,0,1,0};
int Dy[4]={0,1,0,-1};

void dfs(int x,int y,int p)
{
    vis[x][y]=p;
    if(x==n)
    {
        B[p].L=min(B[p].L,y);
        B[p].R=max(B[p].R,y);
    }
    for(int i=0;i<4;i++)
        if(map[x][y]>map[x+Dx[i]][y+Dy[i]]&&vis[x+Dx[i]][y+Dy[i]]!=p)
            dfs(x+Dx[i],y+Dy[i],p);
}

bool cmp(Edge a,Edge b)
{
    return a.L<b.L;
}

int main()
{
    memset(map,0x3f3f3f3f,sizeof(map));
    memset(F,0x3f3f3f3f,sizeof(F));
    cin>>n>>m;
    ans=m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>map[i][j];
    for(int i=1;i<=m;i++)
    {
        B[i].L=0x3f3f3f3f;
        B[i].R=0;
        if((i==1)||(i==m)||(map[1][i]>=map[1][i-1])||(map[1][i]>=map[1][i+1]))
            dfs(1,i,i);
    }
    for(int i=1;i<=m;i++)
        if(vis[n][i]>0) ans--;
    if(ans>0) 
    {
        cout<<0<<endl<<ans<<endl;
        return 0;
    }
    sort(B+1,B+m+1,cmp);
    int L = 1;
    while (L <= m) {
        ans++;
        int R = L;
        for (int k=1; k <= m; k++) {
            if (B[k].L > L) break;
            R = max(R,B[k].R);
        }
        L = R + 1;
    }
    cout<<1<<endl<<ans<<endl;
    return 0;
}

2017.2.18测试

发表评论

2个评论

  • u011046042

    已被收录

    2017-08-26 08:42:53回复

  • DJY1992

    感谢分享,文章已被推荐收录

    2017-05-26 18:04:55回复

我要留言×

技术领域:

我要留言×

留言成功,我们将在审核后加至投票列表中!

提示x

知识工程知识库已成功保存至我的图谱现在你可以用它来管理自己的知识内容了

删除图谱提示×

你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?

删除节点提示×

无法删除该知识节点,因该节点下仍保存有相关知识内容!

删除节点提示×

你确定要删除该知识节点吗?