[解题报告] NOIP 2014 提高组Day1试题

[解题报告] NOIP 2014 提高组Day1试题

1.生活大爆炸版石头剪刀布(rps.cpp/c/pas)

【问题描述】
  石头剪刀布是常见的猜拳游戏:石头胜剪刀,剪刀胜布,布胜石头。如果两个人出拳一样,则不分胜负。在《生活大爆炸》第二季第8集中出现了一种石头剪刀布的升级版游戏。
  升级版游戏在传统的石头剪刀布游戏的基础上,增加了两个新手势:
  斯波克:《星际迷航》主角之一。
  蜥蜴人:《星际迷航》中的反面角色。
  这五种手势的胜负关系如表一所示,表中列出的是甲对乙的游戏结果。
  现在,小A 和小B 尝试玩这种升级版的猜拳游戏。已知他们的出拳都是有周期性规律的,但周期长度不一定相等。例如:如果小 A以“石头 - 布- 石头- 剪刀- 蜥蜴人- 斯波克”长度为6 的周期出拳,那么他的出拳序列就是“石头- 布- 石头- 剪刀- 蜥蜴人- 斯波克- 石头- 布- 石头- 剪刀- 蜥蜴人- 斯波克- ……”,而如果小B 以“剪刀- 石头- 布- 斯波克- 蜥蜴人”长度为5的周期出拳,那么他出拳的序列就是“剪刀- 石头- 布- 斯波克- 蜥蜴人- 剪刀- 石头- 布-斯波克- 蜥蜴人- ……”
  已知小A 和小B 一共进行N 次猜拳。每一次赢的人得1分,输的得0分;平局两人都得0分。现请你统计 N 次猜拳结束之后两人的得分。

【输入】
  输入文件名为rps.in
  第一行包含三个整数:N,Na,Nb,分别表示共进行 N 次猜拳、小 A 出拳的周期长度,小B 出拳的周期长度。数与数之间以一个空格分隔。
  第二行包含Na个整数,表示小 A 出拳的规律,第三行包含 Nb 个整数,表示小 B 出拳的规律。其中,0 表示“剪刀”,1 表示“石头”,2 表示“布”,3 表示“蜥蜴人”, 4 表示“斯波克”。数与数之间以一个空格分隔。
  
【输出】
  输出文件名为rps.out
  输出一行, 包含两个整数,以一个空格分隔,分别表示小A 、小B 的得分。

【数据说明】
  对于100%的数据, 0<N2000<Na2000<Nb200

题目分析:
  签到题,简单的模拟,我们用一个二维数组将5种不同的出拳随机组合而成的25种情况保存下来,并且,我们只保存胜利的情况(用1来表示),输或者平局的情况不保存(用0来表示),统计两者的出拳能获得多少次胜利即可

Code:

#include <bits/stdc++.h>
using namespace std;
int Map[5][5];
int n1[210],n2[210];
int main() {
    int n,na,nb,ans1=0,ans2=0,t1=-1,t2=-1;
    scanf("%d%d%d",&n,&na,&nb);
    Map[0][2]=1;Map[0][3]=1;
    Map[1][0]=1;Map[1][3]=1;
    Map[2][1]=1;Map[2][4]=1;
    Map[3][2]=1;Map[3][4]=1;
    Map[4][0]=1;Map[4][1]=1;
    for(int i=0;i<na;i++) {
        scanf("%d",&n1[i]);
    }
    for(int i=0;i<nb;i++) {
        scanf("%d",&n2[i]);
    }
    for(int i=1;i<=n;i++) {
        if(++t1==na) t1=0;
        if(++t2==nb) t2=0;
        ans1+=Map[n1[t1]][n2[t2]];
        ans2+=Map[n2[t2]][n1[t1]];
    }
    printf("%d %d",ans1,ans2);
}

2.联合权值(link.cpp/c/pas)

【问题描述】
  无向连通图G有 n 个点,n1条边。点从1到 n 依次编号,编号为i的点的权值为Wi,每条边的长度均为1。图上两点 (u,v) 的距离定义为u点到v点的最短距离。对于图G上的点对 (u,v) ,若它们的距离为2,则它们之间会产生 Wu×Wv 的联合权值。 请问图G上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?

【输入】
  输入文件名为link.in
  第一行包含1个整数 n
  接下来n1行,每行包含2个用空格隔开的正整数 uv ,表示编号为 u 和编号为v的点之间有边相连。
  最后1行,包含 n 个正整数,每两个正整数之间用一个空格隔开,其中第i个整数表示图G上编号为i的点的权值为 Wi
  
【输出】
  输出文件名为link.out
  输出共1行,包含2个整数,之间用一个空格隔开,依次为图G上联合权值的最大值和所有联合权值之和
  由于所有联合权值之和可能很大,输出它时要对10007取余

【数据说明】
  对于30%的数据, 1<n100 ; 对于60%的数据, 1<n2000 ; 对于100%的数据, 1<n200,0000<Wi10,000

题目分析:
  首先这是一棵 ,那么我们肯定优先考虑树上DP,题目要求树上两点的距离必须是2,根据无后效性原则,我们大致可以判断每个点直接从父亲处继承。大致的算法思路就出来了!
  我们保存每个节点与它距离为1的所有节点的权值的总和,并且将它父亲节点的权值加两遍(等下告诉你为啥)
  树
  那么让我们来看一下,当前节点为4时的情况, num[2] 中包含着 2w1,w4 w5 ,那么它做出的贡献就是 w4(num[2]w4) ,也就是{4,1}{1,4}和{4,5}。有些人可能要问了,那么{5,4}呢?它就在当前节点为5的时候更新啦!那为什么 w1 就直接更新了两组呢?因为作为4号节点的爷爷节点,他是不可能更新4号节点的,所以我们就直接将 w1 算两遍啦!
  即->
  对于爷爷节点,算两遍(因为爷爷不会回来照顾孙子)
  对于兄弟节点,算一遍(因为兄弟会回来看你)
  这样一遍BFS就过啦!
  还有最大值的问题,对于每个节点,我们在计算 num[i] 时,保存一下最大值和次大值,和最大值来自于哪个节点,当他儿子来访问这个节点时,如果他儿子不是最大值的来源,那么 maxnum=max(maxnum,w[son]first[fa]) ,否则 maxnum=max(maxnum,w[son]second[fa]) ,就是这么简单

Code:

#include <bits/stdc++.h>
using namespace std;
struct Edge {
    int next;
    int to;
}edge[400010];
int head[200010],f[200010],maxn=-210000000,tot=0,w[200010],sum=0,num[200010],first[200010],second[200010],from[200010];
void addedge(int x,int y) {
    edge[++tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
    return ;
}
void search(int son,int fa) {
    if(fa) {
        sum=(long long)(sum+w[son]*(num[fa]-(w[son]%10007)+10007)%10007)%10007;
        if(son!=from[fa]) maxn=max(maxn,w[son]*first[fa]);
        else maxn=max(maxn,w[son]*second[fa]);
    }
    f[son]=fa;
    for(int i=head[son];i;i=edge[i].next) {
        int to=edge[i].to;
        if(w[to]>second[son]) {
            if(w[to]==first[son]) {
                from[son]=0;
            }else if(w[to]>first[son]) {
                second[son]=first[son];
                first[son]=w[to];
                from[son]=to;
            }else {
                second[son]=w[to];
            }
        }
        num[son]=(long long)(num[son]+w[to])%10007;
        if(to==fa) {
            num[son]=(long long)(num[son]+w[to])%10007;
        }
    }
    for(int i=head[son];i;i=edge[i].next) {
        int to=edge[i].to;
        if(to==fa) {
            continue;
        }
        search(to,son);
    }
    return ;
}
int main() {
    int n,x,y;
    scanf("%d",&n);
    for(int i=1;i<n;i++) {
        scanf("%d%d",&x,&y);
        addedge(x,y);
        addedge(y,x);
    }
    for(int i=1;i<=n;i++) {
        scanf("%d",&w[i]);
    }
    search(1,0);
    printf("%d %d",maxn,sum);
}

3.飞扬的小鸟(bird.cpp/c/pas)

【问题描述】
  Flappy Bird 是一款风靡一时的休闲手机游戏,玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败
  为了简化问题,我们对游戏规则进行了简化和改编:
  1.游戏界面是一个长为 n ,高为m的二维平面,其中有 k 个管道(忽略管道的宽度)
  2.小鸟始终在游戏界面内移动。小鸟从游戏界面最左边任意整数高度位置出发,到达游戏界面最右边时,游戏完成
  3. 小鸟每个单位时间沿横坐标方向右移的距离为1,竖直移动的距离由玩家控制。如果点击屏幕,小鸟就会上升一定高度X,每个单位时间可以点击多次,效果叠加;如果不点击屏幕,小鸟就会下降一定高度 Y 。小鸟位于横坐标方向不同位置时,上升的高度X和下降的高度 Y 可能互不相同
  4. 小鸟高度等于0或者小鸟碰到管道时,游戏失败。小鸟高度为m时,无法再上升
  现在,请你判断是否可以完成游戏。如果可以,输出最少点击屏幕数;否则,输出小鸟最多可以通过多少个管道缝隙

【输入】
  输入文件名为 bird.in
  第1行有3个整数 nmk ,分别表示游戏界面的长度,高度和水管的数量,每两个整数之间用一个空格隔开;接下来的 n 行,每行2个用一个空格隔开的整数X Y ,依次表示在横坐标位置0n1上玩家点击屏幕后,小鸟在下一位置上升的高度 X ,以及在这个位置上玩家不点击屏幕时,小鸟在下一位置下降的高度Y。接下来k行,每行3个整数 PLH ,每两个整数之间用一个空格隔开。每行表示一个管道,其中 P 表示管道的横坐标,L表示此管道缝隙的下边沿高度为 L H表示管道缝隙上边沿的高度(输入数据保证P各不相同,但不保证按照大小顺序给出)。

【输出】
  输出文件名为bird.out
  共两行
  第一行,包含一个整数,如果可以成功完成游戏,则输出1,否则输出0
  第二行,包含一个整数,如果第一行为1,则输出成功完成游戏需要最少点击屏幕数,否则,输出小鸟最多可以通过多少个管道缝隙

【数据说明】
  对于30%的数据: 5n105m10k=0 ,保证存在一组最优解使得同一单位时间最多点击屏幕3次;
  对于50%的数据: 5n205m10 ,保证存在一组最优解使得同一单位时间最多点击屏幕3次;
  对于70%的数据: 5n10005m100
  对于100%的数据: 5n100005m10000k<n0<X<m0<Y<m0<P<n 0L<HmL+1<H

题目分析:
  这道题大家一眼就能看出来是一道DP题吧!(笑)
  我们将横坐标 n 作为状态进行转移,由于nm比较巨大,出于能省则省的心理,我们使用滚动数组(毕竟转移只可能来自于前一个)
  这题是一道背包:小鸟在往上飞的时候是完全背包,往下掉的时候是01背包,同时,两者只能选其一
  为了不冲突,先做完全背包(可由前一个状态和自己转移),再做01背包(只能由前一个状态转移),最后将不合法的剔除。为什么要强调最后将不合法的剔除呢?因为如果完全背包前面的(低处)撞墙,他可以飞的更高(转移)来规避,如果你直接将其剔除,那么后面就无法根据它来转移!

Code:

#include <bits/stdc++.h>
using namespace std;
int up[100010],down[100010];
int w_up[100010],w_down[100010];
int f[2][1010];
bool wall[100010];
int read() {
    int ans=0,flag=1;
    char ch=getchar();
    while((ch<'0' || ch>'9') && ch!='-') ch=getchar();
    if(ch=='-') flag=-1,ch=getchar();
    while(ch>='0' && ch<='9') ans=ans*10+ch-'0',ch=getchar();
    return ans*flag;
}
bool change(int& x,int y) {
    if(x>y || x==-1) {
        x=y;
        return true;
    }
    return false;
}
int main() {
    int n,m,k;
    n=read(),m=read(),k=read();
    for(int i=0;i<n;i++) {
        up[i]=read(),down[i]=read();
        w_down[i+1]=0;
        w_up[i+1]=m+1;
    }
    for(int i=1;i<=k;i++) {
        int p=read();
        wall[p]=true;
        w_down[p]=read(),w_up[p]=read();
    }
    memset(f,-1,sizeof(f));
    for(int i=1;i<=m;i++) {
        f[0][i]=0;
    }
    int ans=0;
    bool flag;
    for(int i=1;i<=n;i++) {
        flag=1;
        memset(f[i%2],-1,sizeof(f[i%2]));
        for(int j=1;j<=m;j++) {
            if(j-up[i-1]>0) {
                if(j==m) {
                    for(int l=j-up[i-1]+1;l<=m;l++) {
                        if(f[(i-1)%2][l]!=-1)
                            change(f[i%2][j],f[(i-1)%2][l]+1);
                        if(f[i%2][l]!=-1)
                            change(f[i%2][j],f[i%2][l]+1);
                    }
                }
                if(f[(i-1)%2][j-up[i-1]]!=-1)
                    change(f[i%2][j],f[(i-1)%2][j-up[i-1]]+1);
                if(f[i%2][j-up[i-1]]!=-1)
                    change(f[i%2][j],f[i%2][j-up[i-1]]+1);
            }
        }
        for(int j=1;j<=m;j++) {
            if(j+down[i-1]<=m && f[(i-1)%2][j+down[i-1]]!=-1) {
                change(f[i%2][j],f[(i-1)%2][j+down[i-1]]);
            }
        }
        if(wall[i]) {
            for(int j=1;j<=w_down[i];j++) {
                f[i%2][j]=-1;
            }
            for(int j=w_up[i];j<=m;j++) {
                f[i%2][j]=-1;
            }
        }
        for(int j=1;j<=m;j++) {
            if(f[i%2][j]!=-1) flag=0;
        }
        if(flag) {
            printf("0\n%d",ans);
            return 0;
        }
        if(wall[i]) ans++;
    }
    int minn=2100000000;
    for(int i=1;i<=m;i++) {
        if(f[n%2][i]==-1) continue;
        minn=min(minn,f[n%2][i]);
    }
    printf("1\n%d",minn);
    return 0;
}

  总体来说,NOIP 2014 提高组Day1题目还是比较简单的,希望大家温故而知新,在以后的联赛中考出好成绩 ღ( ´・ᴗ・` )

                                     by:Chlience

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值