CCF 2016 真题 持续更新

8 篇文章 0 订阅

中间数

描述

问题描述
  在一个整数序列a1, a2, …, an中,如果存在某个数,大于它的整数数量等于小于它的整数数量,则称其为中间数。在一个序列中,可能存在多个下标不相同的中间数,这些中间数的值是相同的。
  给定一个整数序列,请找出这个整数序列的中间数的值。
输入格式
  输入的第一行包含了一个整数n,表示整数序列中数的个数。
  第二行包含n个正整数,依次表示a1, a2, …, an。
输出格式
  如果约定序列的中间数存在,则输出中间数的值,否则输出-1表示不存在中间数。
样例输入
6
2 6 5 6 3 5
样例输出
5
样例说明
  比5小的数有2个,比5大的数也有2个。
样例输入
4
3 4 6 7
样例输出
-1
样例说明
  在序列中的4个数都不满足中间数的定义。
样例输入
5
3 4 6 6 7
样例输出
-1
样例说明
  在序列中的5个数都不满足中间数的定义。
评测用例规模与约定
  对于所有评测用例,1 ≤ n ≤ 1000,1 ≤ ai ≤ 1000。

分析

这个还是不同于找中位数的,其实也不要想太多,就按题目的要求,排序后老老实实从中间向两边找,计数到左右不同的数之后,再计数剩下的数是否相同

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
using namespace std;
int num[1005];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i =0;i<n;i++)
        scanf("%d",&num[i]);

        sort(num,num+n);
        int mid = n/2;
        int stand = num[mid];
        int left=mid,right=mid;

        while(left>0&&num[left]==stand)
        {
        left --;
        }

        while(right<n-1 && num[right]==stand)right ++;

        int lpart = left+1, rpart = n-right;

        if(lpart == rpart)printf("%d\n",stand);
        else printf("-1\n");
    }
}

工资计算

描述

问题描述
  小明的公司每个月给小明发工资,而小明拿到的工资为交完个人所得税之后的工资。假设他一个月的税前工资(扣除五险一金后、未扣税前的工资)为S元,则他应交的个人所得税按如下公式计算:
  1) 个人所得税起征点为3500元,若S不超过3500,则不交税,3500元以上的部分才计算个人所得税,令A=S-3500元;
  2) A中不超过1500元的部分,税率3%;
  3) A中超过1500元未超过4500元的部分,税率10%;
  4) A中超过4500元未超过9000元的部分,税率20%;
  5) A中超过9000元未超过35000元的部分,税率25%;
  6) A中超过35000元未超过55000元的部分,税率30%;
  7) A中超过55000元未超过80000元的部分,税率35%;
  8) A中超过80000元的部分,税率45%;
  例如,如果小明的税前工资为10000元,则A=10000-3500=6500元,其中不超过1500元部分应缴税1500×3%=45元,超过1500元不超过4500元部分应缴税(4500-1500)×10%=300元,超过4500元部分应缴税(6500-4500)×20%=400元。总共缴税745元,税后所得为9255元。
  已知小明这个月税后所得为T元,请问他的税前工资S是多少元。
输入格式
  输入的第一行包含一个整数T,表示小明的税后所得。所有评测数据保证小明的税前工资为一个整百的数。
输出格式
  输出一个整数S,表示小明的税前工资。
样例输入
9255
样例输出
10000
评测用例规模与约定
  对于所有评测用例,1 ≤ T ≤ 100000。

分析

这题乍一看挺简单的,本来的思路是手算出对应的T的范围,然后将输入的T按照范围换元成S,结果算着算着就晕了……
看了网上的参考,充分体会到了什么叫程序员数学不好,,真的没必要计算出具体的数,只要列出公式,找出抽象的共性和规律,然后剩下的交给计算机来算就可以了
还是列出式子,表达出S的表达式,然后就直接用S的范围来圈定就可以,感觉思路十分新奇
代码如下

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
using namespace std;
    float rate[]={0.03,0.1,0.2,0.25,0.3,0.35,0.45};
    float revenue[] = {1500*rate[0],(4500-1500)*rate[1],(9000-4500)*rate[2],(35000-9000)*rate[3],(55000-35000)*rate[4],(80000-55000)*rate[5]};
int main()
{
    int T;

    while(~scanf("%d",&T)){

    float S=(float)T,res=(float)T;

    if(T>3500)
    {
    //其实列出两三个就知道规律是什么了
        S =( T - rate[0]*3500)/(1-rate[0]);
        if( S-3500>0 && S-3500<=1500)res = S;

        S = (T + revenue[0] - rate[1]*(3500+1500))/(1-rate[1]);
        if( S-3500 > 1500 && S-3500<=4500)res = S;

        S = (T + revenue[0]+revenue[1] - rate[2]*(3500+4500))/(1-rate[2]);
        if(S - 3500 >4500 && S-3500 <=9000)res = S;

        S = (T + revenue[0]+revenue[1]+revenue[2] - rate[3]*(3500+9000))/(1-rate[3]);
        if(S - 3500 > 9000 && S- 3500 <=35000)res = S;

        S = (T+revenue[0]+revenue[1]+revenue[2] + revenue[3] - rate[4]*(3500+35000))/(1-rate[4]);
        if(S - 3500 >35000 && S- 3500 <=55000)res = S;

        S = (T +revenue[0]+ revenue[1]+revenue[2] + revenue[3] + revenue[4]- rate[5]*(3500+55000))/(1-rate[5]);
        if(S-3500 > 55000 && S-3500 <=80000)res = S;

        S = (T +revenue[0]+ revenue[1]+revenue[2] + revenue[3] + revenue[4] + revenue[5] - rate[6]*(3500 + 80000))/(1-rate[6]);
        if(S - 3500 >80000)res = S;

    }
    int ans = (int)res;
    printf("%d\n",ans);
    }
}

压缩编码

描述

问题描述
  给定一段文字,已知单词a1, a2, …, an出现的频率分别t1, t2, …, tn。可以用01串给这些单词编码,即将每个单词与一个01串对应,使得任何一个单词的编码(对应的01串)不是另一个单词编码的前缀,这种编码称为前缀码。
  使用前缀码编码一段文字是指将这段文字中的每个单词依次对应到其编码。一段文字经过前缀编码后的长度为:
  L=a1的编码长度×t1+a2的编码长度×t2+…+ an的编码长度×tn。
  定义一个前缀编码为字典序编码,指对于1 ≤ i < n,ai的编码(对应的01串)的字典序在ai+1编码之前,即a1, a2, …, an的编码是按字典序升序排列的。
  例如,文字E A E C D E B C C E C B D B E中, 5个单词A、B、C、D、E出现的频率分别为1, 3, 4, 2, 5,则一种可行的编码方案是A:000, B:001, C:01, D:10, E:11,对应的编码后的01串为1100011011011001010111010011000111,对应的长度L为3×1+3×3+2×4+2×2+2×5=34。
  在这个例子中,如果使用哈夫曼(Huffman)编码,对应的编码方案是A:000, B:01, C:10, D:001, E:11,虽然最终文字编码后的总长度只有33,但是这个编码不满足字典序编码的性质,比如C的编码的字典序不在D的编码之前。
  在这个例子中,有些人可能会想的另一个字典序编码是A:000, B:001, C:010, D:011, E:1,编码后的文字长度为35。
  请找出一个字典序编码,使得文字经过编码后的长度L最小。在输出时,你只需要输出最小的长度L,而不需要输出具体的方案。在上面的例子中,最小的长度L为34。
输入格式
  输入的第一行包含一个整数n,表示单词的数量。
  第二行包含n个整数,用空格分隔,分别表示a1, a2, …, an出现的频率,即t1, t2, …, tn。请注意a1, a2, …, an具体是什么单词并不影响本题的解,所以没有输入a1, a2, …, an。
输出格式
  输出一个整数,表示文字经过编码后的长度L的最小值。
样例输入
5
1 3 4 2 5
样例输出
34
样例说明
  这个样例就是问题描述中的例子。如果你得到了35,说明你算得有问题,请自行检查自己的算法而不要怀疑是样例输出写错了。
评测用例规模与约定
  对于30%的评测用例,1 ≤ n ≤ 10,1 ≤ ti ≤ 20;
  对于60%的评测用例,1 ≤ n ≤ 100,1 ≤ ti ≤ 100;
  对于100%的评测用例,1 ≤ n ≤ 1000,1 ≤ ti ≤ 10000。

分析

看起来像哈夫曼编码的问题,但是实际上不是。哈夫曼编码过程中每次都取最小的两堆,但是这里要求保持字典序也即是只能取相邻的两个
因此就变成了动态规划中的石子问题,跟矩阵链乘也比较像
dp[i][j]记录合并第i堆到第j堆的花费,sum[j]表示从起始到第j堆总共多少石头
那么dp[i][j]= min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j])
三层循环,找到k即可

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxx = 1005;
int dp[maxx][maxx];
int sum[maxx];
const int inf = 1<<30;
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        int t;
        for(int i = 0;i<n;i++)
        {
            scanf("%d",&t);
            if(i==0)sum[i]=t;
            else sum[i]=sum[i-1]+t;
            dp[i][i]=0;
        }

        for(int l = 1;l<n;l++)
        {
            for(int i = 0;i<n-l;i++)
            {
                int j = i+l;
                dp[i][j]=inf;
                int temp = sum[j]-(i>0?sum[i-1]:0);
                for(int k = i;k<j;k++)
                {
                    if(dp[i][j]> dp[i][k]+dp[k+1][j]+temp)
                    dp[i][j]=dp[i][k]+dp[k+1][j]+temp;
                }
            }
        }
        printf("%d\n",dp[0][n-1]);
    }
}

火车购票

描述

问题描述
  请实现一个铁路购票系统的简单座位分配算法,来处理一节车厢的座位分配。
  假设一节车厢有20排、每一排5个座位。为方便起见,我们用1到100来给所有的座位编号,第一排是1到5号,第二排是6到10号,依次类推,第20排是96到100号。
  购票时,一个人可能购一张或多张票,最多不超过5张。如果这几张票可以安排在同一排编号相邻的座位,则应该安排在编号最小的相邻座位。否则应该安排在编号最小的几个空座位中(不考虑是否相邻)。
  假设初始时车票全部未被购买,现在给了一些购票指令,请你处理这些指令。
输入格式
  输入的第一行包含一个整数n,表示购票指令的数量。
  第二行包含n个整数,每个整数p在1到5之间,表示要购入的票数,相邻的两个数之间使用一个空格分隔。
输出格式
  输出n行,每行对应一条指令的处理结果。
  对于购票指令p,输出p张车票的编号,按从小到大排序。
样例输入
4
2 5 4 2
样例输出
1 2
6 7 8 9 10
11 12 13 14
3 4
样例说明
  1) 购2张票,得到座位1、2。
  2) 购5张票,得到座位6至10。
  3) 购4张票,得到座位11至14。
  4) 购2张票,得到座位3、4。
评测用例规模与约定
  对于所有评测用例,1 ≤ n ≤ 100,所有购票数量之和不超过100。

分析

模拟这个思路就好了,数量级都不大,就正常思路就可以。用数组表示,用101以后的数表示已经卖出的票

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
using namespace std;
int tickets[105][10];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        int cnt = 1;
        for(int i = 1;i<=n;i++)
        for(int j = 1;j<=5;j++)
        tickets[i][j] = cnt++;
        int num;

        for(int i = 0;i<n;i++)
        {
            bool flag = false;
            scanf("%d",&num);
            for(int r = 1;r<=n;r++)
            {
                int c;
                for(c = 1;c<=5;c++)
                {
                    if(tickets[r][c]<101 )
                    {
                        if(6-c>=num)
                        {
                            flag = true;
                            int k = 0;
                            while(k<num){
                                printf("%d ",tickets[r][c+k]);
                                tickets[r][c+k]=105;k++;
                            }
                            printf("\n");
                            break;
                        }

                    }

                }
                if(flag)break;
            }


            if(flag == false)
            {
                int k = 0; bool flag2 = false;
                for(int r = 1;r<=n;r++)
                {
                    for(int c = 1;c<=5;c++)
                    {
                    if(tickets[r][c]<101)
                    {
                        k++;printf("%d ",tickets[r][c]);
                        tickets[r][c] = 105;
                        if(k>=num){flag2 = true;break;
                        }
                    }
                    }
                    if(flag2)break;
                }
                printf("\n");

            }

        }
    }
}

交通规划

描述

问题描述
  G国国王来中国参观后,被中国的高速铁路深深的震撼,决定为自己的国家也建设一个高速铁路系统。
  建设高速铁路投入非常大,为了节约建设成本,G国国王决定不新建铁路,而是将已有的铁路改造成高速铁路。现在,请你为G国国王提供一个方案,将现有的一部分铁路改造成高速铁路,使得任何两个城市间都可以通过高速铁路到达,而且从所有城市乘坐高速铁路到首都的最短路程和原来一样长。请你告诉G国国王在这些条件下最少要改造多长的铁路。
输入格式
  输入的第一行包含两个整数n, m,分别表示G国城市的数量和城市间铁路的数量。所有的城市由1到n编号,首都为1号。
  接下来m行,每行三个整数a, b, c,表示城市a和城市b之间有一条长度为c的双向铁路。这条铁路不会经过a和b以外的城市。
输出格式
  输出一行,表示在满足条件的情况下最少要改造的铁路长度。
样例输入
4 5
1 2 4
1 3 5
2 3 2
2 4 3
3 4 2
样例输出
11
评测用例规模与约定
  对于20%的评测用例,1 ≤ n ≤ 10,1 ≤ m ≤ 50;
  对于50%的评测用例,1 ≤ n ≤ 100,1 ≤ m ≤ 5000;
  对于80%的评测用例,1 ≤ n ≤ 1000,1 ≤ m ≤ 50000;
  对于100%的评测用例,1 ≤ n ≤ 10000,1 ≤ m ≤ 100000,1 ≤ a, b ≤ n,1 ≤ c ≤ 1000。输入保证每个城市都可以通过铁路达到首都。

分析

看起来像是单源最短路径和最小生成树的结合版
必须满足额条件是单源最短路径不变的情况下,新增的铁路最少
所以还是要求得从源点到各个点之间额最短路径长度,当有多个相同长度的路径可以到达时,选择新增长度最小的。关键还是理解题意
用邻接表表示图,存储Edge结构体表示到达的下一个节点和边长,用优先队列按最短的距离保存节点,这样每次选择的都是最近的
然后更新dis数组,用Minedge记录到新增的节点N最短的边,最后求和

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int citymax = 10005;
struct Edge{
    int to,dis;
    Edge(int to, int dis):to(to),dis(dis){
    };
};
struct Node{
    int to,dis;
    Node(int t, int d):to(t),dis(d){
    };
    friend bool operator<(const Node & a,const Node &b)
    {
        return a.dis>b.dis;
    }
};
vector<Edge>G[citymax];
priority_queue<Node>que;
int minedge[citymax];
bool visit[citymax];
int dis[citymax];
int n,m,a,b,c;
const int inf = 1<<30;
void dij()
{
    for(int i = 1;i<=n;i++)
    {
        visit[i]=false;
        dis[i]=inf;
        minedge[i]=inf;
    }
    visit[1]=1;
    dis[1]=0;minedge[1]=0;
    que.push(Node(1,0));
    while(!que.empty())
    {
        Node now = que.top();
        que.pop();
        int t = now.to;
        int di = now.dis;
        visit[t]=true;
        for(int i = 0;i<G[t].size();i++)
        {
            int de =G[t][i].to;
            int edge = G[t][i].dis;
            if(dis[de]>dis[t]+edge)
            {
                dis[de] = dis[t]+edge;
                minedge[de] = edge;
                que.push(Node(de,dis[de]));
            }
            if(dis[de]==dis[t]+edge)
            {
                minedge[de] = min(minedge[de],edge);
            }
        }
    }
}
int main()
{

    while(~scanf("%d%d",&n,&m))
    {
        while(m--){
            scanf("%d%d%d",&a,&b,&c);
            G[a].push_back(Edge(b,c));
            G[b].push_back(Edge(a,c));
        }
        dij();
        int ans = 0;
        for(int i = 2;i<=n;i++)
        {
            ans += minedge[i];
        }
        printf("%d\n",ans);
    }
}

折点计数

描述

问题描述
  给定n个整数表示一个商店连续n天的销售量。如果某天之前销售量在增长,而后一天销售量减少,则称这一天为折点,反过来如果之前销售量减少而后一天销售量增长,也称这一天为折点。其他的天都不是折点。如下图中,第3天和第6天是折点。

  给定n个整数a1, a2, …, an表示销售量,请计算出这些天总共有多少个折点。
  为了减少歧义,我们给定的数据保证:在这n天中相邻两天的销售量总是不同的,即ai-1≠ai。注意,如果两天不相邻,销售量可能相同。
输入格式
  输入的第一行包含一个整数n。
  第二行包含n个整数,用空格分隔,分别表示a1, a2, …, an。
输出格式
  输出一个整数,表示折点出现的数量。
样例输入
7
5 4 1 2 3 6 4
样例输出
2
评测用例规模与约定
  所有评测用例满足:1 ≤ n ≤ 1000,每天的销售量是不超过10000的非负整数。

分析

就是求斜率好了,在斜率符号发生变化的地方就是折点;
而相邻两点的横坐标都是1,所以斜率就变成了后一个数-前一个数
统计发生符号变化的地方

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
using namespace std;
int num[1005];
int k[1005];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i =1;i<=n;i++)
        {
            scanf("%d",&num[i]);
        }
        int pos = 0;
        for(int i = 2;i<=n;i++)
        {
            k[pos] = (num[i]-num[i-1]);
            pos ++;
        }
        int ans = 0;
        for(int i = 1;i<pos;i++)
        {
            if(k[i]*k[i-1]<0)ans++;
        }
        cout<<ans<<"\n";
    }
}

俄罗斯方块

描述

问题描述
  俄罗斯方块是俄罗斯人阿列克谢·帕基特诺夫发明的一款休闲游戏。
  游戏在一个15行10列的方格图上进行,方格图上的每一个格子可能已经放置了方块,或者没有放置方块。每一轮,都会有一个新的由4个小方块组成的板块从方格图的上方落下,玩家可以操作板块左右移动放到合适的位置,当板块中某一个方块的下边缘与方格图上的方块上边缘重合或者达到下边界时,板块不再移动,如果此时方格图的某一行全放满了方块,则该行被消除并得分。
  在这个问题中,你需要写一个程序来模拟板块下落,你不需要处理玩家的操作,也不需要处理消行和得分。
  具体的,给定一个初始的方格图,以及一个板块的形状和它下落的初始位置,你要给出最终的方格图。
输入格式
  输入的前15行包含初始的方格图,每行包含10个数字,相邻的数字用空格分隔。如果一个数字是0,表示对应的方格中没有方块,如果数字是1,则表示初始的时候有方块。输入保证前4行中的数字都是0。
  输入的第16至第19行包含新加入的板块的形状,每行包含4个数字,组成了板块图案,同样0表示没方块,1表示有方块。输入保证板块的图案中正好包含4个方块,且4个方块是连在一起的(准确的说,4个方块是四连通的,即给定的板块是俄罗斯方块的标准板块)。
  第20行包含一个1到7之间的整数,表示板块图案最左边开始的时候是在方格图的哪一列中。注意,这里的板块图案指的是16至19行所输入的板块图案,如果板块图案的最左边一列全是0,则它的左边和实际所表示的板块的左边是不一致的(见样例)
输出格式
  输出15行,每行10个数字,相邻的数字之间用一个空格分隔,表示板块下落后的方格图。注意,你不需要处理最终的消行。
样例输入
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 1 0 0 0
1 1 1 0 0 0 1 1 1 1
0 0 0 0 1 0 0 0 0 0
0 0 0 0
0 1 1 1
0 0 0 1
0 0 0 0
3
样例输出
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 1 0 0 0
1 1 1 1 1 1 1 1 1 1
0 0 0 0 1 1 0 0 0 0

分析

千万不要想复杂了,还是从最简单的开始想起,就是数字为1的位置下落过程中要判断下一行的这一列是不是1,都不是的话行数就可以加一
所以记录小块中所有1的位置,然后每次行加一,直到碰到0或者触底
然后这些位置处标1输出即可

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
int Panel[20][15];
int board[5][5];
int newPanel[20][15];
int main()
{
    for(int i = 1;i<=15;i++)
    for(int j = 1;j<=10;j++)
    {
        scanf("%d",&Panel[i][j]);
    }
    int x[5],y[5];
    int cnt = 0;
    for(int i = 1;i<=4;i++)
    {
        for(int j = 1;j<=4;j++)
        {scanf("%d",&board[i][j]);  }
    }
        int s;
    scanf("%d",&s);
    for(int i = 1;i<=4;i++)
    for(int j = 1;j<=4;j++)
    {
        if(board[i][j]==1)
        {
            x[cnt]=i;
            y[cnt]= s+j-1;
            cnt ++;
        }
    }
    while(true)
    {
        if(Panel[x[0]+1][y[0]]==0 && Panel[x[1]+1][y[1]]==0 &&
        Panel[x[2]+1][y[2]]==0 && Panel[x[3]+1][y[3]]==0)
        {
            x[0]++;x[1]++;x[2]++;x[3]++;
        }
        else break;
        if(x[0]==15 || x[1]==15 || x[2]==15 || x[3]==15)break;
    }
    for(int i = 0;i<4;i++)
    {
        Panel[x[i]][y[i]]=1;
    }
    for(int i = 1;i<=15;i++)
    {
        for(int j = 1;j<=10;j++)
            printf("%d ",Panel[i][j]);
        printf("\n");
    }   
}

游戏

描述

问题描述
  小明在玩一个电脑游戏,游戏在一个n×m的方格图上进行,小明控制的角色开始的时候站在第一行第一列,目标是前往第n行第m列。
  方格图上有一些方格是始终安全的,有一些在一段时间是危险的,如果小明控制的角色到达一个方格的时候方格是危险的,则小明输掉了游戏,如果小明的角色到达了第n行第m列,则小明过关。第一行第一列和第n行第m列永远都是安全的。
  每个单位时间,小明的角色必须向上下左右四个方向相邻的方格中的一个移动一格。
  经过很多次尝试,小明掌握了方格图的安全和危险的规律:每一个方格出现危险的时间一定是连续的。并且,小明还掌握了每个方格在哪段时间是危险的。
  现在,小明想知道,自己最快经过几个时间单位可以达到第n行第m列过关。
输入格式
  输入的第一行包含三个整数n, m, t,用一个空格分隔,表示方格图的行数n、列数m,以及方格图中有危险的方格数量。
  接下来t行,每行4个整数r, c, a, b,表示第r行第c列的方格在第a个时刻到第b个时刻之间是危险的,包括a和b。游戏开始时的时刻为0。输入数据保证r和c不同时为1,而且当r为n时c不为m。一个方格只有一段时间是危险的(或者说不会出现两行拥有相同的r和c)。
输出格式
  输出一个整数,表示小明最快经过几个时间单位可以过关。输入数据保证小明一定可以过关。
样例输入
3 3 3
2 1 1 1
1 3 2 10
2 2 2 10
样例输出
6
样例说明
  第2行第1列时刻1是危险的,因此第一步必须走到第1行第2列。
  第二步可以走到第1行第1列,第三步走到第2行第1列,后面经过第3行第1列、第3行第2列到达第3行第3列。
评测用例规模与约定
  前30%的评测用例满足:0 < n, m ≤ 10,0 ≤ t < 99。
  所有评测用例满足:0 < n, m ≤ 100,0 ≤ t < 9999,1 ≤ r ≤ n,1 ≤ c ≤ m,0 ≤ a ≤ b ≤ 100。

分析

参考 https://blog.csdn.net/csdn_blog_lcl/article/details/53428415
BFS判断最优,使用队列
用struct存储时间上限下限,判断能不能走的时候需要查看是否在危险时间内
同一时刻内一个点走一次就够了,所以可以判断这个优化
明确:什么情况下是可走的点;可走的点要修改什么标记,标记修改顺序要统一;入队还是出队;什么情况下找到了答案;怎么判断下一个行走方向

代码


#include<iostream>
#include<queue>
#include<windows.h>
using namespace std;

struct Data
{
    int r;  //顶点所在行
    int c;  //顶点所在列
    int time; //走到顶点时的时间
    int mintime;  //危险时间的下界
    int maxtime;  //危险时间的上界
    //  弄两个构造函数,方便初始化数据
    Data(){}
    Data(int x1,int x2,int x3,int x4,int x5)
    {
        r = x1;
        c = x2;
        time = x3;
        mintime = x4;
        maxtime = x5;
    }
};
struct Data data[101][101];

int timeChanged(int row,int col,int time) // 判断某个顶点的是否已经更新过了,更新过的顶点就不需要再入队
{
    if(data[row][col].time == time)
    {
        return 1;
    }
    return 0;
}

int isDanger(int row,int col,int ctime)  // 判断这个顶点是否安全
{
    if(ctime >= data[row][col].mintime && ctime <= data[row][col].maxtime)
    {
        return 1;
    }
    return 0;
}

int main()
{
    int n,m,t;
    int i,j;
    cin>>n>>m>>t;
    for(i = 1; i <= n; i++)  //  初始化顶点数据
    {
        for(j = 1; j <= m; j++)
        {
            data[i][j] = Data(i,j,-1,-1,-1);
        }
    }
    for(i = 1; i <= t; i++)  //输入数据
    {
        int r,c,a,b;
        cin>>r>>c>>a>>b;
        data[r][c].mintime = a;
        data[r][c].maxtime = b;
    }
    data[1][1].time = 0;
    queue<Data> q;
    q.push(data[1][1]);
    while(!q.empty())   // 使用队列广度优先搜索的思想,并加上限制条件
    {
        struct Data datanow = q.front();
        q.pop();
        int ctime = datanow.time;
        int row = datanow.r;
        int col = datanow.c;

        // 向右走
        if(col < m && !isDanger(row,col+1,ctime+1) && !timeChanged(row,col+1,ctime+1))
        {
            data[row][col+1].time = ctime + 1;
            q.push(data[row][col+1]);
        }
        //  向上走
        if(row > 1 && !isDanger(row-1,col,ctime+1) && (ctime+1) && !timeChanged(row-1,col,ctime+1))
        {
            data[row-1][col].time = ctime + 1;
            q.push(data[row-1][col]);
        }
        //  向左走
        if(col > 1 && !isDanger(row,col-1,ctime+1) && !timeChanged(row,col-1,ctime+1))
        {
            data[row][col-1].time = ctime + 1;
            q.push(data[row][col-1]);
        }
        //  向下走
        if(row < n && !isDanger(row+1,col,ctime+1) && !timeChanged(row+1,col,ctime+1))
        {
            data[row+1][col].time = ctime + 1;
            q.push(data[row+1][col]);
        }
        if(data[n][m].time != -1)  //点(n,m)的时间更新了,说明到点(n,m)的最短时间出来了,退出循环
        {
            cout<<data[n][m].time;
            break;
        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值