中南大学第十一届大学生程序设计竞赛总结

1.Tricky数

题目要求链接

  • 分析
    要注意审题,注意:这种数字在不重复利用每一位的条件下,可以提取出k(k>0)个子序列,而且保证提取出这k个子序列都是”233”,且不再剩余任何数字。当初我就是没有认真审题就导致花费了很长时间都没有做出来,后来又读了几遍题,才真正理解题意。
    题意基本就是给出一个最大长度为1000的字符串,让你判断该字符串是否可以全部分解为233的子序列,且最后不在包含任何数字。

  • 解题思路1:
    从左向右循环考虑每一个2,由于我们是从左向右来考虑,因此最佳策略就是选择这个2之后最近的没有被配对的两个3与之配对,并将它们标记为已经配对。
    最后再检查是否所有字符都被打上已配对的标记

  • 我的代码

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

bool a[1000];       //保存标记位 
char c[1000];       //保存字符串 
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        bool flag = true;
        memset(a,0,sizeof(a));
        scanf("%s",c);
        int len = strlen(c);

        /*从左向右循环考虑每一个2,由于我们是从左向右来考虑,
        因此最佳策略就是选择这个2之后最近的没有被配对的两个3与之配对,
        并将它们标记为已经配对。*/
        for(int i=0; i<len; i++)
        {
            int location1=-1,location2=-1;
            if(c[i] == '2')
            {
                for(int j=i+1; j<len; j++)
                {
                    if(c[j]=='3' && a[j]==0)
                    {
                        if(location1 == -1)
                        {
                            location1 = j;
                            continue;
                        }
                        location2 = j;
                        a[i] = 1;
                        a[location1] = 1;
                        a[location2] = 1;
                        break;
                    } 

                }
            }
        } 

        //检查是否所有字符都被打上已配对的标记
        for(int i=0; i<len; i++)
        {
            //printf("%d\n",a[i]);
            if(a[i]==0) flag = false;
        }

        if(flag){
            printf("Yes\n");
        }else{
            printf("No\n");
        }

    }
    return 0;
} 
  • 解题思路二:
    参考我们学校另一位同学的思想,点击查看
    我才知道可以在O(N)时间复杂度内完成。就是从左到右扫描字符串,分别记录‘2’和‘3’出现的次数,进行相应操作即可。
  • 代码
#include<iostream>
#include<cstring>
using namespace std;

char a[1000];
int main()
{
    int T;
    scanf("%d",&T);

    while(T--)
    {
        scanf("%s",a);
        int len = strlen(a);

        bool flag = true;
        int cnt2 = 0, cnt3 = 0;
        for(int i=0; i<len; i++)
        {
            if(a[i] == '2')
            {
                cnt2++;
            }else if(a[i] == '3')
            {
                if(cnt2 == 0)
                {
                    flag = false;
                    break;
                }
                if(cnt3 == 1)
                {
                    cnt2--;
                    cnt3 = 0;
                }else{
                    cnt3++;
                }
            }else
            {
                flag = false;
                break;
            }
        }

        if(cnt2!=0 || cnt3!=0)  flag = false;

        if(flag){
            printf("Yes\n");
        }else{
            printf("No\n");
        }

    }
    return 0;
}
  • 题目评价:
    考查内容: 基本字符串操作、贪心
    时间复杂度:O(n^2)、O(n)
    题目难度: ☆

2.锋芒不露

题目要求

  • 分析
    题意不太好理解,需要多读几遍。
    其实题意就是让左右剑两两组合,使得组合中和的最大值尽可能小。那么可以想到,要使和的最大值尽可能小,那么必然是左剑的最小值和右剑的最大值组合,然后左剑的次小值和右剑的次大值组合,这样依次进行两两组合,那么这些和中最大的值就是我们所要求的答案。
    如果左手剑的数值是a[1,2,3,…,n] , 右手剑的数值是b[1,2,3…,n]。我们显然可以想到,先把两个数组a[],b[]排序,然依次将a[i]和b[n-i-1]求和,最后max{a[i]+b[n-i-1]}就是所求的值。且不说排序所要花费的时间,单单是对1-n时,数据比较n次,时间复杂度就是O(N^2)。而N是1e5级别的,N^2就是1e10。一般来说,1e8运算所需要的时间是1秒钟,这样显然会超时。
    那么该怎么办呢?注意分析题,我们可以发现,剑的值不超过100,所以可以用100大小左右数组记录每一个值存在多少把剑,左手从小到大,右手从大到小,不断交替减掉匹配的次数,更新当前匹配的最大值。基本就是一种桶排序的思想。

  • 我的代码

//#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

//left[i]表示左手剑中攻击力值为i的个数。right[j]表示右手剑中攻击力值为j的个数 
int left[101],right[101];

int main()
{
    int N;
    while(~scanf("%d",&N))
    {
        if(N == 0)  break;

        //memset函数给int类型数组赋值只能赋为-1或0,因为 该函数是逐字节赋值的 
        memset(left,0,sizeof(left));        
        memset(right,0,sizeof(right));

        int x,y;
        for(int k=1; k<=N; k++)
        {
            scanf("%d%d",&x,&y);
            left[x]++;  right[y]++;

            int maxSum = 0;
            int match = 0;
            int location1 = 1, location2 = 100;
            int cishu1 = left[location1], cishu2 = right[location2];

            while(match != k)
            {
                if(cishu1 == 0)
                {
                    cishu1 = left[++location1];
                    continue;
                }
                if(cishu2 == 0)
                {
                    cishu2 = right[--location2];
                    continue;
                }

                if(location1+location2 > maxSum )
                {
                    maxSum = location1+location2;
                }

                if(cishu1 == cishu2)
                {       
                    match += cishu1;    
                    cishu1 = left[++location1];
                    cishu2 = right[--location2];

                }else if(cishu1 < cishu2)
                {
                    match += cishu1;
                    cishu2 -= cishu1;
                    cishu1 = left[++location1];
                }else if(cishu1 > cishu2)
                {
                    match += cishu2;
                    cishu1 -= cishu2;
                    cishu2 = right[--location2];
                }                       
            } 

            printf("%d\n",maxSum);
        }
        printf("\n");
    }
}

我的代码这里把#include<iostream>注释掉了,换成了#include<cstdio>是因为iostream中已经定义过了left和right。而cstdio头文件中没有。

  • 题目评价
    考查内容:贪心,计数
    时间复杂度 O(n*100)
    题目难度:☆☆☆

3.复盘拉火车

  • 题目要求
    题目链接
  • 题目分析
    模拟整个游戏过程即可。
    由于一个玩家“吃”掉的牌会按照原来的顺序放入自己牌堆的最后面,符合队列“先进先出”的特点,因此可以用队列来实现“吃牌”操作。
    其次可以开一个数组来记录每种牌在桌面上出现的位置(蕴含着该牌已经在桌面上出现),这样当再次放牌时就知道了“吃牌”区间。
  • 我的代码
#include<iostream>
#include<queue>
#include<string>
using namespace std;

const int LEN = 13;
queue<string> GJ;
queue<string> XS;
string a[LEN];          //a[]保存桌面上的牌 

int main()
{
    int T;
    cin>>T;
    int N1,N2,K;
    string c;


    //初始化a[] 
    for(int i=0; i<LEN; i++)
    {
        a[i] = "0"; //"0"表示什么都没有 
    } 

    while(T--)
    {   

        //读入数据,并放到相应的队列中 
         cin>>N1;   //scanf("%d",&N1);
         for(int i=0; i<N1; i++)
         {
            cin>>c;     //scanf("%s",&c[0]);
            GJ.push(c);
         }

         cin>>N2;       //scanf("%d",&N2);
         for(int i=0; i<N2; i++)
         {
            cin>>c;     //scanf("%s",&c[0]);
            XS.push(c);
         }

         //进行K次放牌 
         scanf("%d",&K);
         for(int i=0; i<K; i++)
         {
            if(i%2 == 0)
            {
                c = GJ.front();     GJ.pop();
                for(int j=0; j<LEN; j++)
                {
                    //找到了相同的牌 
                    if(c == a[j])
                    {
                        for(int k=j; k<LEN && a[k]!="0"; k++)
                        {
                            GJ.push(a[k]);
                            a[k] = "0";
                        }
                        GJ.push(c);
                        break;
                    }
                    //没有找到相同的牌 
                    if(a[j] == "0")
                    {
                        a[j] = c;
                        break;
                    }
                }
            }else{
                c = XS.front();     XS.pop();
                for(int j=0; j<LEN; j++)
                {
                    //找到了相同的牌 
                    if(c == a[j])
                    {
                        for(int k=j; k<LEN && a[k]!="0"; k++)
                        {
                            XS.push(a[k]);
                            a[k] = "0";
                        }
                        XS.push(c);
                        break;
                    }
                    //没有找到相同的牌 
                    if(a[j] == "0")
                    {
                        a[j] = c;
                        break;
                    }
                }
            }
         }

         //输出,同时顺便清空GJ、XS、a[]中的牌 
         cout<<"Deck:";              //printf("Deck:");
         for(int i=0; i<LEN&&a[i]!="0"; i++)
         {
            cout<<" "<<a[i];        //printf(" %s",a[i]);
            a[i] = "0"; 
         }
         cout<<endl;                //printf("\n");

         cout<<"GJ:";               //printf("GJ:");
         while(!GJ.empty())
         {
            cout<<" "<<GJ.front();       //printf(" %s",GJ.front());
             GJ.pop();  
         } 
         cout<<endl;                //printf("\n");

         cout<<"XS:";                   //printf("XS:");
         while(!XS.empty())
         {
            cout<<" "<<XS.front();  //printf(" %s",XS.front());
            XS.pop();   
         } 
          cout<<endl;           //printf("\n");

          cout<<endl;//         printf("\n");
    }

    return 0;
}

我用scanf和printf输入输出字符串有问题。。一直搞不懂问题出在哪儿。。

  • 题目评价:
    考查内容:基本数据结构的使用
    时间复杂度:O(n)
    题目难度:☆ ☆

  • 我的体会
    就这三道题,其实都不难,但比赛的时候我只做出来一道,而且是自己错了好几次才做出来。
    我觉得首先要认真读题,除了要真正理解题目的意思,还要注意一些数据的范围,经常是这些范围限定了不能用哪些方法来解题。
    其次我慢慢觉得代码风格也要注意,首先要尽可能精简准确,能一句话表达清楚的绝不用三五行,不要写得太过冗长,但同时不容易理解的地方也要写注释。写注释不仅可以给别人看的时候会好些,更重要的是可以帮助自己理清思路,知道自己每一步要做什么。精简和注释并不矛盾。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值