• 2020-程序设计语言综合设计


    所有代码已上传GitHub

    解题说明

    1.5致死一击:

    致死一击

    Kunkun最近热爱rpg闯关游戏,经常带着他的舍友打各种boss。但是随着舍友装备的逐渐升级,kunkun发现他给予boss最后一击的机会越来越少(给boss最后一击的玩家稀有装备爆率会大幅度提升)。所以kunkun联系到了一个神秘人,他可以利用时停来让boss躲过舍友的攻击,每次时停只能躲避一次攻击。 假设kunkun和他的舍友都采取轮流攻击战术(kunkun率先攻击,kunkun的攻击力为a;舍友的攻击力为b,玩家每次都只进行一次攻击)去刷n个boss。如果最多只能使用k次时停,那么kunkun能造成致死伤害的boss最多有几个?

    输入格式:

    输入共两行,第一行包括4个正整数 n,a,b,k (1≤n≤2*1e5, 1≤a,b,k≤1e9),n表示boss的数量,a为kunkun的攻击力,b为kunkun舍友的攻击力,k为时停的最大次数。

    第二行输入n个正整数h1,h2,…,hn (1≤hi≤1e9),表示每个boss的血量。

    输出格式:

    输出一个整数,即kunkun造成致死伤害的boss的最大个数。


    分析:

    首先,通过题目得知,kunkun的目的在于要在boss的血量小于或等于自己的攻击力时才算击败此boss,所以在boss血量小于自己血量之前,在轮流攻击,恰巧下一次攻击时队友的攻击力会将怪兽打死,所以,在这一段特殊回合内,kunkun需要通过开启时停,来保证自己只有自己对怪输出伤害。所以我的思路是首先对每一只boss求出打死他需要开几次时停,并将结果存到另一个数组里。然后对新数组排序,从需要开启时停最少的次数开始,当时停次数减去下一只boss需要开启时停次数,就可以求出最多可以杀死几只boss。


    程序核心代码(或伪代码):

    for(int i = 0; i < n; i++) {
            if(s[i] % (a + b) == 0) {    //a攻击后,如果恰好队友杀死boss
                if((a + b) % a == 0) {   //如果a恰好杀死boss,那么次数需要减一
                    x[i] = (a + b) / a - 1;
                }
                else {                   //反之则不需要减一
                    x[i] = (a + b) / a;
                }
            }
            else {                       //a攻击后,队友没有杀死boss,但是下一次a攻击后,boss可能死亡
                if((s[i] % (a + b)) % a == 0) {
                    x[i] = (s[i] % (a + b)) / a - 1;  //同上
                }
                else {
                    x[i] = (s[i] % (a + b)) / a;      //同上
                }
            }
        }
        sort(x, x + n);                  //进行排序
        for(int i = 0; i < n; i++) {
            if(k - x[i] >= 0) {
                k -= x[i];
                count++;                 //计算打死的boss
            }
        }
        cout << count;
    

    心得:这题开始以为就是模拟打boss过程,没有注意到其实最后需要的打boss的最多个数,不一定在每次都开时停,所以第一次就的七十分,后面重新审视题目,发现错误,才发现错误想到方法。其实这题并不难,难度主要集中于对于问题的理解上面。

    1.7德州扑克:

    德州扑克

    最近,阿夸迷于德州扑克。所以她找到了很多人和她一起玩。由于人数众多,阿夸必须更改游戏规则:

    1. 所有扑克牌均只看数字,不计花色。
    2. 每张卡的值为1、2、3、4、5、6、7、8、9、10、11、12、13 中的一种(对应A,2、3、4、5、6、7, 8、9、10,J,Q,K)
    3. 每位玩家从一副完整的扑克牌(没有大小王)中抽出五张扑克牌,可能出现的手牌的值从低到高排列如下:
    • 高牌:不包含以下牌的牌。对于都是高牌的牌,按照五张牌的值的和进行从大到小排序。
    • 对子:手中的5张牌中有2张相同值的牌。对于都拥有对子的牌,按构成该对子的牌的值进行从大到小地排序。如果这些都相同,则按手牌中余下3张牌的值的和进行从大到小排序。
    • 两对:手中拥有两对不同的对子。对于都包含两对的手牌,按其最高对子的值进行从大到小排序。如果最高对子相同,则按另一个对子的值从大到小地进行排序。如果这些值相同,则按剩余牌的值从大到小地进行排序。
    • 三条:手中拥有3张相同值的牌。对于都包含三条的手牌按构成三条的牌的值进行从大到小地排序。如果这些值相同,则按剩余牌的值从大到小地进行排序。
    • 满堂红:手中拥有一个三条和一个对子。同理,先按三条大小排序,如果三条大小相同,则按对子大小进行排序。
    • 四条:手中拥有4张相同值的牌。对于都包含四条的手牌按构成四条的牌的值进行从大到小地排序。如果这些值相同,则按剩余牌的值从大到小地进行排序。
    • 顺子:手中拥有5张连续值的卡。对于都包含顺子的手牌按顺子最大的牌进行排序。
    • 皇家同花顺:手中拥有10到A(10、J、Q、K、A)。是最大的手牌!

    现在,阿夸已经知道了每个人的手牌,她想要知道所有人的排名列表。如果玩家的手牌大小相等,则按玩家名字的字典序输出。保证没有重复的名字。你能帮帮她吗?

    输入格式:

    第一行包含一个正整数 N (1<=N<=100000) ,表示玩家的人数。

    接下来 N 行,每行包含两个字符串:m (1<=|m|<=10 ) ,表示玩家的名字;s (1<=|s|<=10),表示玩家的手牌。

    输出格式:

    输出 N个玩家的排名列表。、


    分析:

    这题难度之所以大是因为同时出现多种难点需要去攻克,首先是输入的人名以及卡片,我将其存在结构体变量中:

    struct Player {
    	string name;
    	int card[5];
    	int c1,c2,c3,c4;
    }player[100005];
    

    第一步需要将字符串的卡片转换成数字的卡片,代码如下:

    for(int i=0;i<n;i++) {                         //将牌由字符转化成数字存在player[i].card[5]里
    		char ca[12];
    		int j=0;
    		cin >> player[i].name >> ca;
    		int k=0;
    		while(ca[j]!=0) {
    			if(ca[j]=='A') {
    				player[i].card[k++]=1;
    			}
    			else if(ca[j]=='J') {
    				player[i].card[k++]=11;
    			}
    			else if(ca[j]=='Q') {
    				player[i].card[k++]=12;
    			}
    			else if(ca[j]=='K') {
    				player[i].card[k++]=13;
    			}
    			else if(ca[j]=='1'&&ca[j+1]=='0') { //这里需要对10作特殊处理
    				player[i].card[k++]=10;
    				j++; //记得j要多自增一次
    			}
    			else {
    				player[i].card[k++]=ca[j]-'0';
    			}
    			j++;
    		}
    		sort(player[i].card,player[i].card+5,cmp1);
    	}
    

    接着是较为复杂的部分,这里需要对题目所给的八种情况作出判断,输出卡片大小从大到小,所以第二步是要计算出现次数超过一次的卡片是什么,出现了几次做出判断,这里我用了两个过程来实现,首先,利用一个temp[14]大小的数组存储,对于card[5]里的数,每出现一个数相应的temp数组对应的数自增,然后再用t1[5]、t2[2]、t3[1]、t4[1]数组存储下对应的出现几次的数分别为什么。ps:这里出现了一点问题,数组需要初始化一下,不然会是乱码,我以为直接声明的数组默认是全为零的,但是其实不是这样。这一部分代码如下:

    //这里的judge_1和judge_2函数是用来判断是否为皇家同花顺或者是顺子,相关代码也较为简单,所以我就不贴出来了
    if(judge_1(player[i].card)) {  
    			player[i].c1=8;
    			continue;
    		}
    		if(judge_2(player[i].card)) {
    			player[i].c1=7;
    			player[i].c2=player[i].card[0];
    			continue;
    		}
    		int temp[14],t1[5],t2[2],t3[1],t4[1],tt1=0,tt2=0,tt3=0,tt4=0;
    		for(int j=1;j<14;j++) {
    			temp[j]=0;
    		}
    		for(int j=0;j<5;j++) {
    			t1[j]=0;
    		}
    		t2[0]=0;t2[1]=0;t3[0]=0;t4[0]=0;  //这边改了一下,把数组初始化,下面测试输出发现不初始化的话数组里面不会是0
    		for(int j=0;j<5;j++) {
    			temp[player[i].card[j]]++; //利用temp[14]记下相同数字出现次数
    		}
    		for(int k=1;k<14;k++) { //分别用四个数组记下出现次数分别为1、2、3、4次的数,并且这些数默认是从小到大的,所以不需要再次排序
    			if(temp[k]>=4) {
    				t4[tt4++]=k;
    			}
    			else if(temp[k]==3) {
    				t3[tt3++]=k;
    			}
    			else if(temp[k]==2) {
    				t2[tt2++]=k;
    			}
    			else if(temp[k]==1) {
    				t1[tt1++]=k;
    			}
    		}
    

    由此,我们可以开始判断除了皇家同花顺和顺子以外的情况了。第三部分,我借鉴了我们班吕航dalao的代码,利用结构体里的c1、c2、c3、c4四个来体现优先级,判断直接陈述有点难以理解,所以直接上代码:

    if(t4[0]!=0) {    //四条
    			player[i].c1=6;
    			player[i].c2=t4[0];
    			if(t1[0]!=0) { //这里需要一个特判,从老师那里了解到最后两个点有出现五张一样的牌的情况
    				player[i].c3=t1[0];
    			}
    			else {
    				player[i].c3=t4[0];
    			}
    			continue;
    		}
    		if(t3[0]!=0&&t2[0]!=0) {   //满堂红
    			player[i].c1=5;
    			player[i].c2=t3[0];
    			player[i].c3=t2[0];
    			continue;
    		}
    		if(t3[0]!=0) {    //三条
    			player[i].c1=4;
    			player[i].c2=t3[0];
    			player[i].c3=t1[0]+t1[1];
    			continue;
    		}
    		if(t2[0]!=0&&t2[1]!=0) {   //两对
    			player[i].c1=3;
    			player[i].c2=t2[1];
    			player[i].c3=t2[0];
    			player[i].c4=t1[0];
    			continue;
    		}
    		if(t2[0]!=0) {      //对子
    			player[i].c1=2;
    			player[i].c2=t2[0];
    			player[i].c3=t1[0]+t1[1]+t1[2];
    			continue;
    		}
    		player[i].c1=1;      //高牌
    		player[i].c2=t1[0]+t1[1]+t1[2]+t1[3]+t1[4];
    

    现在,player已经记载每一个情况对应的优先级,我们利用下面这个函数,可直接用C++的排序STL进行排序:

    bool cmp2(Player a,Player b) {
    	if(a.c1==b.c1) {
    		if(a.c2==b.c2) {
    			if (a.c3==b.c3) {
    				if(a.c4==b.c4) {
    					return a.name<b.name;
    				}
    				return a.c4>b.c4;
    			}
    			return a.c3>b.c3;
    		}
    		return a.c2>b.c2;
    	}
    	return a.c1>b.c1;
    }
    

    直接输出结果就可以了。


    心得:这题难度较高,主要体现在将多个难点整合到了一起,这样代码长度增加,在阅读和debug上都增加了难度,所以总结一点经验,就是要多写注释,否则对于像我这样的人,打代码过程中刷一下手机就看不懂上面的代码了,其次,在解决问题过程中,可以拿一张纸记一下思想方法什么的,有了一个大概的方向,这样可以加快速度。

    2.4美丽数列

    题目:

    小明是个普通的计算机本科生,很喜欢研究数组相关的问题。在他的认知里,美丽的数组是这样的,对于一个长度为n的数组a,存在一个下标i(1<=i<=n)使得1i之间的数是严格递增的,i+1n之间的数是严格递减的。现在这个数组a里的元素是随机给定的(这个数组可能是不美丽的),对于数组a内的任意一个元素ai我们可以进行若干次ai=ai-1(ai>0)的操作,问能否通过若干次操作使得这个数组变得美丽。

    输入格式:

    第一行输入数组长度n (1≤n≤3*1e5), 第二行输入n个整数a1,…,an (0≤ai≤1e9)。

    输出格式:

    输出“Yes”表示这个数组可以变美丽,输出“No”表示不可以。


    分析:

    每一个数都存在是最大值的可能,而题目给出的条件实际上不是要求我们判断这个数列是不是完美数列,而实际上要求我们判断的是这数列他会不会不可能变成一个完美数列。首先假设这个数列它不可能是完美数列,那么,从它的最高点开始向两边递减,且相邻差值只为1,这样,就出现了这个数列不是完美数列的情况,即定义中的,要求数列中所有值大于0,由此我们得到最极限的情况,即假设从两端开始,每个数向中间递增1,这样就是完美数列的标准模板。进而,在深挖其中的规律,则可以得到,每个数的值都必须满足这个数的值要大于这个数的数组下标到数组两端点的距离。只要两个端点同时不满足,即可得出该数列不是完美数列,即可得出结论。

    ps:犯错误了,这里需要判断奇偶性,假设中间按照上面判断,如果是偶数个的话,那么中间两个数的值就会一样,这样就不行,就要判断此时的中间两个数必须有一个数要大于原来的数+1,所以需要加一个判断来处理。

    程序核心代码(或伪代码):

    for(int i = 1; i <= (n - 1) / 2; i++) {
        if(n % 2 == 0 && i == (n - 1) / 2) {
            if((a[i] < i && a[n - i - 1] < i + 1) || (a[i] < i + 1 && a[n - i - 1]<i)) {
                cout << "No";
                return 0;
                sign = 0;
            }
        }
        if(a[i] < i || a[n - i - 1] < i) {
            cout << "No";
            return 0;
            sign = 0;
        }
    }
    

    心得:做这类题目,所做的并不是去模拟他操作数列达到完美数列的过程,因为所给的数据大小实在过大,去模拟显得十分不现实,我们需要完成的就是去剖析完美数列的本质,这样,才可以从根本上简单快捷的得出答案。

    2.5最长公共子串

    题面:

    给定两个字符串a、b,现有k次机会对字符串中的字符进行修改,使修改后两个字符串的最长公共子串最长。每一次修改,可以选择a、b字符串中某一个串的任意位置修改成任意字符。

    输入格式:

    第一行包括一个正整数 k。

    第二行和第三行分别输入字符串a、b。(每个串的长度不超过500)

    输出格式:

    输出为一个整数,表示修改后的两个串的最长公共子串长度。


    看到这一题,去查了之后,先用的是动态规划,但是后面发现不行,所以直接暴力枚举法,这题的根本目的在于,给定两个字符串,给k次机会使得这两个字符串不一样,我们需要知道使用了k次机会后,这两个字符串一样的长度是多少,我们要比较许多个这样的字符串,最后得出他们中最长的那一个字符串。思路清晰:

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    char a[505],b[505];
    int Calculate(char* a, char* b, int k) {
        int pos = 0;
        while((a[pos] != 0) && (b[pos] != 0) && k != 0) {
            if(a[pos] == b[pos]) {
                pos++;
            }
            else {
                k--;
                pos++;
            }
        }
        return pos;
    }
    int main() {
        int k, alen, blen, temp, ans=0;
        cin >> k;
        cin >> a >> b;
        alen = strlen(a);
        blen = strlen(b);
        for(int i = 0; i < alen; i++) {
            for(int j = 0; j < blen; j++) {// 二维数组下标从1开始
                temp = Calculate(a + i, b + j, k);
                if(temp > ans) {
                    ans = temp;
                }
            }
        }
        cout << ans;
    }
    
    

    2.6旋转骰子

    题目

    玛莎有n个骰子,每个骰子的6个面上都恰好有一个0到9之间的数字。

    现在玛莎将利用这n个筛子来制作新数字。她把n个骰子摆成一排,然后从左到右查看骰子的上表面并读取,即可得到一个新数字。随后她不断的旋转每个骰子的面就可以得到不同的新数字。旋转骰子需要满足以下规则: 1、制作的数字不能包含前导零; 2、制作新数字时不需要使用所有的骰子; 3、使用骰子旋转,无法将数字9转换为数字6,反之亦然。

    给定n个骰子,玛莎可以用它们构成从1到x的所有整数。玛莎想知道,对于给定的n个骰子,这个x的最大取值是多少呢?

    输入格式:

    第一行仅一个整数n,表示骰子的数量(1≤n≤3)。

    接下来n行,每行包含6个整数a[i][j](0≤a[i][j]≤9),表示第i个骰子的第j个面上的数字。

    输出格式:

    输出一个整数,即最大数x,玛莎可以使用她的骰子构成数字从1到x。如果无法构成1,则输出0。


    分析:

    首先,可以知道只有三颗骰子,那么组成的数必定在三位数以内,三位数内的计算,计算机的速度飞快,就可以直接枚举出三个骰子可以列举出的所有的数,然后用一个长度为1000的数组来记录,当我们用骰子枚举出一个数的时候,让数组[枚举的数]自增1,在输出的时候,从一开始,一个一个判断该数是否非零,当发现该数为零时,直接结束,并输出此时的数组下标减一就可以了


    程序核心代码(或伪代码):

    for(int i = 0; i < n; i++) {
            for(int j = 0; j < 6; j++) {
                check[a[i][j]]++;
            }   
        }
    if(n>1) {
    	for(int i = 0; i < 6; i++) {
        	for(int j = 0; j < 6; j++) {
            	check[a[0][i] * 10 + a[1][j]]++;
                check[a[1][i] * 10 + a[0][j]]++;
                check[a[0][i] * 10 + a[2][j]]++;
                check[a[2][i] * 10 + a[0][j]]++;
                check[a[1][i] * 10 + a[2][j]]++;
                check[a[2][i] * 10 + a[1][j]]++;
            }
        }
    }
    if(n>2) {
    	for(int i = 0; i < 6; i++) {
            for(int j = 0; j < 6; j++) {
                for(int k = 0; k < 6; k++) {
                    check[a[0][i] * 100 + a[1][j] * 10 + a[2][k]]++;
                    check[a[0][i] * 100 + a[2][j] * 10 + a[1][k]]++;
                    check[a[1][i] * 100 + a[0][j] * 10 + a[2][k]]++;
                    check[a[1][i] * 100 + a[2][j] * 10 + a[0][k]]++;
                    check[a[2][i] * 100 + a[0][j] * 10 + a[1][k]]++;
                    check[a[2][i] * 100 + a[1][j] * 10 + a[0][k]]++;
                }
            }
        }
    }
    
    
    

    心得:

    这题需要认识到题目仅让我们求三个骰子可以组合成的情况,因此枚举出来这些数,再作判断,循环判断在哪一点间断即可。有的时候这样的枚举显得傻楞,但是在数据量较小的时候,我们理应首先考虑枚举。

    2.7糖果传递

    有n个小朋友坐成一圈,每人有 颗糖果。每人只能给左右两人传递糖果。每人每次传递一颗糖果的代价为 。求使所有人获得均等糖果的最小代价。

    输入格式:

    第一行有一个整数 ,表示小朋友个数;

    在接下来 行中,每行一个整数 。、

    输出格式

    输出使所有人获得

    均等糖果的最小代价。


    先记下每个人的糖果数为Ai,然后记Xi为第i个人给第i-1个人的糖果,然后每个人的糖果就和平均值有一个等式:Ai-Xi+Xi+1=aver,对于每一个小朋友,我们一共有n个方程:

    A1-X1+X2=aver

    A2-X2+X3=aver

    ······

    An-1-Xn-1+Xn=aver

    An-Xn+X1=aver (这个我们其实并不需要)

    我们只需要取前n-1个式子,就可以把X2到Xn用X1表示出来,这也是明确我们目的是求X1+···+Xn,然后我们可以通过上下式依次加减得到:

    X2=X + aver - A1 C1=A1 - aver

    X3=X + 2aver - A1 - A2 C2=A1 + A2 - 2aver

    X4=X + 3aver - A1 - A2 - A3 C3=A1 + A2 + A3 - 3aver

    ······

    Xn=X + (n - 1)aver - A1 - A2 - ······ - An-1  Cn-1=A1 + A2 + ······ + An-1 - (n - 1)aver

    然后,还是记住最终目的是求X1+···+Xn,我们将X2到Xn用含X1替换,就可以知道我们要使|X1| + |X1-C1| + |X1-C2| + ……+ |X1-Cn-1|要尽量小,可以看到,即求一个点X1,使得这个点X1到所有点Ci的距离之和最小,而这个点的值可以对C数组排序后去中位数得到。

    实现:

    #include <iostream>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    int A[1000005],C[1000005],sum[1000005];
    int main() {
    	long long int n, aver = 0, ans = 0, Cmid;
    	cin >> n;
    	for(int i = 1; i <= n; i++) {
    		cin >> A[i];
    		sum[i] = sum[i-1] + A[i];
    		aver += A[i];
    	}
    	aver /= n;
    	for(int i = 1; i <= n - 1; i++) {
    		C[i] = sum[i] - i * aver;
    	}
    	C[n] = 0; // 这里加上C[n]=0是为了添加|X1-0|的情况
    	sort(C + 1, C + n + 1);
    	Cmid = C[n / 2];
    	for(int i = 1; i <= n; i++) {
    		ans += abs(Cmid - C[i]);
    	}
    	cout << ans;
    }
    
    

    3.3不诚实的卖家

    题目

    伊戈尔发现有一家商店正在打折,所以决定在这家商店购买n件商品。商店的打折活动会持续一周,打折期间每件商品的价格是ai,等打折活动结束后,商品的价格变为bi。但是并非所有卖家都诚实,因此打折期间某些商品的价格可能会比折扣活动结束后的价格更贵。

    伊戈尔决定现在至少购买k件商品,剩下的商品等活动结束后再购买。你的任务是帮伊戈尔计算一下用于购买n件商品的最低费用。

    输入格式:

    第一行包含两个正整数n和k(1≤n≤2e5,0≤k≤n),分别表示伊戈尔要购买的商品数量和他现在只少要买的商品数。

    第二行包含n个整数 a1,a2,...,an(1≤ai≤1e4),分别表示折扣期间各个商品的价格。

    第三行包含n个整数 b1,b2,...,bn(1≤bi≤1e4),分别表示折扣结束后商品的价格。

    输出格式:

    伊戈尔购买n件商品所需的最低金额。

    输入样例:

    3 1
    1 3 5
    6 4 2
    
    

    输出样例:

    6
    
    

    分析:

    首先观察到,一样商品有活动前价格和活动后价格,而且这两个价格可能需要处理后排序,所以需要考虑用结构体来完成。再看到,有的商品之前价格可能大于之后价格,所以需要引入一个量,作为排序的依据,这里,我选用之后价格减去之前价格,来完成排序。题目设置的坑来了,那就是至少提取k次,也就是说前k次一定在之前买,之后的因为已经排过序,所以可以直接按最节省的价格购买,由此,分析完毕。

    程序核心代码(或伪代码):

    #include<iostream>
    #include<algorithm>
    using namespace std;
    struct S{
        int before;
        int after;
        int m;
    } s[200005];
    bool cmp(S a, S b) {
        return a.m > b.m;
    }
    int main() {
        int n, k, sum = 0;
        cin >> n >> k;
        for(int i = 0; i < n; i++) {
            cin >> s[i].before;
        }
        for(int i = 0; i < n; i++) {
            cin >> s[i].after;
            s[i].m = s[i].after - s[i].before;
        }
        sort(s, s+n, cmp);
        for(int i = 0; i < k; i++) {
            sum += s[i].before;
        }
        for(int i = k; i < n; i++) {
            if(s[i].m > 0) {
                sum += s[i].before;
            }
            else {
                sum += s[i].after;
            }
        }
        cout << sum;
    }
    
    

    心得:

    一定要理解清楚题意再开始打代码,不要急功近利,现在脑海里构建起框架,再去实施,效率会更高;

    3.4回文数

    对于一个自然数n,若将n的各位数字反向排列所得的数n1与n相等,则称n为回文数,例如2332。

    若给定一个N( 2<=N<=16)进制数M(M的长度在一百位以内),如果M不是回文数,可以对其进行N进制加法,最终得到回文数。

    例如对于十进制数79 STEP1 : 79 + 97 = 176 STEP2 : 176 + 671 = 847 STEP3 : 847 + 748 = 1595 STEP4 : 1595 +5951 = 7546 STEP5 : 7546 + 6457 = 14003 STEP6 : 14003 + 30041 = 44044

    那么对于给定的N进制数M,请判断其能否在30步以内(包括30步)得到回文数。

    输入格式:

    第一行包括一个正整数 N(2<=N<=16)。

    第二行包括一个正整数M(一百位以内)。

    输出格式:

    如果可以在n步内得到回文数,输出“STEP=n”,否则输出“NO”。

    输入样例1:

    10
    79   
    
    

    输出样例1:

    STEP=6 
    
    

    输入样例2:

    8
    665556
    
    

    输出样例2:

    NO
    
    

    分析:

    在学习一点点C++过后,可以使用C++的常用STL中的reverse来实现回文数的判断,所以可以轻松写到一个判断函数,然后还有高精度加法函数,本题的难点在于输入进制不一定为十进制,所以不能用一般的高精度模板,需先将数字按进制存储到数组里,在对数组进行高精度运算,其中的进位制也要发生改变,我们需要在高精度加法函数中传入一个进制,用于计算和换算,还有可以使用reverse直接实现相反字符串的生成,因为本题最多考虑到16进制,所以可直接引入sixt字符数组来存储进制数。由此,分析完毕。

    程序核心代码(或伪代码):

    #include <bits/stdc++.h>
    using namespace std;
    char sixt[20] = "0123456789ABCDEF";
    int judge(string a) {
        string b = a;
        reverse(b.begin(),b.end());
        if(a == b) {
            return 1;
        }
        else {
            return 0;
        }
    }
    string super_add(int k, string b) { //高精度加法过程,推广到 k 进制
        string a = b;
        reverse(a.begin(), a.end());
        int numa[101], numb[101], numc[101];
        int len = a.length(), lenc = 1;
        string ans;
        for (int i = 0; i < len; i++) {
            if (isdigit(a[i])) numa[len - i] = a[i] - '0'; //对于十六进制的特判,如果是数字减去 0 ,下同
            else numa[len - i] = a[i] - 'A' + 10; //如果不是数字减去 A 再加上 10
            if (isdigit(b[i])) numb[len - i] = b[i] - '0';
            else numb[len - i] = b[i] - 'A' + 10;
        }
        int x = 0;
        while (lenc <= len) {
            numc[lenc] = numa[lenc] + numb[lenc] + x;
            x = numc[lenc] / k; //除以 k,x是进位的数
            numc[lenc] %= k; //k 取模,代表就是这位数了
            lenc++;
        }
        numc[lenc] = x;
        while (numc[lenc] == 0) lenc--;
        for (int i = lenc; i >= 1; i--) ans += sixt[numc[i]];
        return ans;
    }
    int main() {
        int n;
        string m;
        cin >> n;
        cin >> m;
        for(int i = 0; i <= 30; i++) {
            if(judge(m)) {
                cout << "STEP=" << i;
                return 0;
            }
            else {
                m=super_add(n, m);
            }
        }
        cout << "NO";
    }
    
    

    心得:

    本体为实现功能区分了几块功能,我们需要对功能进行拆分,分成各个函数,各司其职,达到面向对象的编程思想,其次用到如reverse和isdigit等常用STL,使得代码简练许多,也方便省事。


    4.4特殊的翻译

    小明的工作是对一串英语字符进行特殊的翻译:当出现连续且相同的小写字母时,须替换成该字母的大写形式,在大写字母的后面紧跟该小写字母此次连续出现的个数;与此同时,把连续的小写字母串的左侧和右侧的字符串交换位置;重复该操作,直至没有出现连续相同的小写字母为止。现在小明想请你帮他完成这种特殊的翻译。

    输入格式:

    输入一串由小写字母构成的字符串。(字符串长度不大于250)

    输出格式:

    输出翻译后的字符串。

    输入样例1:

    dilhhhhope
    
    

    输出样例1:

    在这里给出相应的输出。例如:

    opeH4dil
    
    

    输入样例2:

    lodnkmgggggggoplerre
    
    

    输出样例2:

    在这里给出相应的输出。例如:

    eG7lodnkmR2ople
    
    

    分析:

    首先,寻找字符串中出现超过一次的小写字母(需去除数字),循环记录出现的次数,出现的左端,右端,以及是什么字母,利用string类型的特点,分别记录前面部分和后面部分,然后重新交换前后部分的字符,这里需要注意出现的次数不一定小于十,如出现大于9的情况就不可用出现次数加上"0"来解决,此时需要一个函数将相应的数字转化成字符串来解决,接着不断重复以上过程直到字符串中不再出现超过一次的小写字母,就退出搜索。

    程序核心代码(或伪代码):

    #include <iostream>
    #include <sstream>
    using namespace std;
    string change(int count) {//转化int型为string型
        stringstream str;
        str << count;
        return str.str();
    }
    int main() {
        string str;
        cin >> str;
        while(1) {
            int i = 1, markleft, markright, count, check = 0;
            while(str[i]) {
                if(str[i] == str[i - 1] && str[i] > 'a' && str[i] < 'z') {
                    check = 1;
                    markright = markleft = i;
                    count = 1;
                    while(str[markright] == str[markleft]) {
                        count++;
                        markright++;
                    }
                    string strleft(str, 0, markleft - 1);
                    string strright(str, markright);
                    string num = change(count);
                    str = strright + char(str[markleft] - 32) + num + strleft;
                    i += count;
                    break;
                }
                i++;
            }
            if(check == 0) {
                cout << str;
                return 0;
            }
        }
    }
    
    

    心得:

    在做题时需要考虑周全,需要考虑各种情况,例如出现的次数会超过9次,这样会导致出错,所以在提交之前应当自己做一定测试,来测试代码的正确性。


    4.5好吃的巧克力

    超市正在特价售卖巧克力,正好被贪吃的Lucky_dog看见了。

    巧克力从左到右排成一排,一共有N个,M种。

    超市有一个很奇怪的规定,就是你在购买巧克力时必须提供两个数字a和b,代表你要购买第 a 个至第 b 个巧克力(包含 a 和 b)之间的所有巧克力。

    假设所有巧克力的单价均为1元。Lucky_dog想吃所有种类的巧克力,但又想省钱。作为Lucky_dog的朋友,他请你来帮他决定如何选择购买巧克力时的 a 和 b。

    输入格式:

    第一行包含两个正整数 N 和 M(M<=N, N<=10^6 , M<=2000),分别代表巧克力的总数及种类数。

    第二行包含 N 个整数,这些整数均在1 至 M 之间,代表对应的巧克力所属的种类。

    输出格式:

    输出仅一行,包含两个整数a和 b(a<=b) ,由一个空格隔开,表示花费最少且包含所有种类巧克力的购买区间。

    数据保证有解,如果存在多个符合条件的购买区间,输出a最小的那个。

    输入样例:

    12 5
    2 5 3 1 3 2 4 1 1 5 4 3
    
    

    输出样例:

    在这里给出相应的输出。例如:

    2 7
    
    

    分析:

    如果直接采用模拟的方法,暴力枚举出每一种情况如何存储到结构体数组里,由于数据量过大,必然会导致超时的发生,于是采取优化算法的方法,在尝试的过程中,想到了在输入时,先将巧克力的种类和该种类的数量记录在一个数组中,首先对右端进行左移,一旦该区间的最右端巧克力种类大于1,就将该数量巧克力减一,并左移一位,再从左端向右端检查,作相同处理,本以为此时即是答案,但是检查后发现,这样初步处理只能得到位置最靠前的一个种类最全的区间,并不是题目要求的种类最全且长度最短且位于最左端的区间,为了得到这个区间,继续对区间进行右移,首先对右端点右移一位,再对左端点进行符合条件的左移操作,如果此时区间的长度最短,就记录下来,反之则继续下一次右移,最后全部右移完毕之后,输出答案即可。

    程序核心代码(或伪代码):

    #include <iostream>
    using namespace std;
    int a[1000005],zl[2005] = {0};
    int main() {
        int n,m;
        cin >> n >> m;
        for(int i = 0; i < n; i++) {
            cin >> a[i];
        }
        int tempnum = 0, left = 0, right = 0;
        while(tempnum != m) {
            if(zl[a[right]] == 0) {
                tempnum++;
            }
            zl[a[right]]++;
            right++;
        }
        while(zl[a[left]] > 1) {
            zl[a[left]]--;
            left++;
        }
        int signleft = left, signright = right;
        while(right < n) {
            zl[a[right]]++;
            right++;
            while(zl[a[left]] > 1) {
                zl[a[left++]]--; 
            }
            if(signright - signleft > right - left) {
                signleft = left;
                signright = right;
            }
        }
        cout << signleft + 1 << ' ' << signright;
    }
    
    

    心得:

    暴力枚举法不是所有时候都可以实现要求,在不能解决后需要及时转换思路,换一个方法来解决问题,就不应困在如何优化暴力枚举的算法,这样是不能解决根本的效率问题。


    5.2微信号

    小明刚认识了新同学小红,他想要小红的微信号,小红不想直接告诉他,所以给了小明一串加密了的数字,并且把解密规则告诉了小明。

    解密规则是:首先删除第1个数,接着把第2个数放在这串数的最后面,再删除第3个数,并把第4个数放在这串数的最后面……直至只剩最后一个数,把最后一个数也删除。

    按照删除的顺序,把这些数字连在一起就是小红的微信号。请你按照解密规则帮小明得到小红的微信号。

    输入格式:

    第一行包括一个正整数n(1 < n < 500),表示这串微信号的长度;

    第二行包括n个数字,即加密的小红的微信号。

    输出格式:

    输出解密后的微信号,相邻数字之间有空格。

    输入样例:

    9
    1 2 3 4 5 6 7 8 9
    
    

    输出样例:

    1 3 5 7 9 4 8 6 2
    
    

    分析:

    利用队列的思想,设置两个队列,首先是que队列用于储存题目所给的数据,第二个是答案队列,用于存储答案,对于存储队列,设置一个sign的值模拟每次是奇数次还是偶数次,每次分奇偶,如果删除就进入ans队列,删除q队列的头部,如果是连接到队列后,则将头部先推入尾部,再删除头部。最后输出ans队列的头部即完成

    程序核心代码(或伪代码):

    #include <iostream>
    #include <queue>
    using namespace std;
    queue<int> que;
    queue<int> ans;
    int main() {
        
        int n;
        cin >> n;
        for(int i = 0; i < n; i++) {
            int temp;
            cin >> temp;
            que.push(temp);
        }
        int sign = 1;
        while(!que.empty()) {
            if(sign == 1) {
                sign = 0;
                ans.push(que.front());
                que.pop();
            }
            else {
                sign = 1;
                que.push(que.front());
                que.pop();
            }
        }
        while(!ans.empty()) {
            cout << ans.front() << ' ';
            ans.pop();
        }
    } 
    
    

    心得:

    模拟队列来思考问题有时会使思路更加清楚


    5.4谁比我大

    给定一个含有n个整数的数列a1,a2,...an。定义函数 f(ai)表示数列中第i个元素ai之后第一个大于ai的元素的下标,若这样的元素不存在,则f(ai)=0。

    输入格式:

    第一行包含一个正整数n(n<=1e6);

    第二行包含n个正整数 a1,a2,...an(1<=ai<=1e9)。

    输出格式:

    输出仅一行包含 n个整数,分别代表 f(ai) 的值。

    输入样例:

    5
    1 4 2 3 5
    
    

    输出样例:

    2 5 4 5 0
    
    

    分析:

    不明白使用队列和栈要怎么做,所以采用的是正常做法,利用两个循环,每遍历一个数时就向后遍历,直到出现比该数大的数为止,将该数赋值给原来的数组,节约空间,不必再开一个数组,若没有数比该数大则直接赋值为0

    程序核心代码(或伪代码):

    #include <iostream>
    using namespace std;
    int a[1000005];
    int main() {
        int n;
        cin >> n;
        for(int i = 0; i < n; i++) {
            cin >> a[i];
        }
        for(int i = 0; i < n; i++) {
            int temp = 0;
            for(int j = i + 1; j < n; j++) {
                if(a[j] > a[i]) {
                    temp = j + 1;
                    break;
                }
            }
            a[i] = temp;
        }
        for(int i = 0; i < n; i++) {
            cout << a[i] << ' ';
        }
    }
    
    

    心得:

    这题还没有领会如何用栈或者来完成,希望可以了解一下做法,增长一下知识。

    6.3括号匹配调整

    如果通过插入“ +”和“ 1”可以从中得到格式正确的数学表达式,则将带括号的序列称为正确的。

    例如,序列 "(())()","()"和 "(()(()))"是正确的,而")(","(()))("和"(()" 不是。

    定义重新排序操作:选择括号序列的任意连续子段(子字符串),然后以任意方式对其中的所有字符进行重新排序。

    当重新排序的子段的长度为t时,重新排序操作需要耗时t秒。

    例如,对于“))((”,他可以选择子字符串“)(”并重新排序“)()(”(此操作将花费2秒)。

    不难看出,重新排序操作不会改变左括号和右括号的数量。

    现在,LD想花费最少的时间,通过任意次数(可能为零)执行重新排序操作来使括号序列变成正确的。

    输入格式:

    第一行包含一个整数n(1≤n≤1e6),表示序列的长度;

    第二行包含一个长度为n的字符串,仅由字符‘(’和‘)’组成。

    输出格式:

    输出一个整数,表示使括号序列正确的最小秒数;如果不可能实现,则输出-1。

    输入样例:

    8
    ))((())(
    
    

    输出样例:

    6
    
    

    分析:

    这题需要利用栈来解决,首先可以得知,合法的情况是,如果栈空,那么“(”情况可以直接入栈,反之“)”入栈为非法,如果栈非空,那么“(”可以直接入栈,而“)”则无需入栈,且此时需要弹出栈顶元素,相当于一对括号完成匹配,可以去掉。由题意可以知道,对括号的重新排序操作,我们只需要直到非法的括号子列长度即可,无需关心对其如何排序,那么问题就转化成为求括号序列内非法子列的长度,再由前面判断非法的方法可以知道,一旦遇到栈空且“)”入栈,那么此时为非法状态,此时再次进入栈的均算入非法序列,列举一下规律可得知,我们只需将非法状态下入栈的“(”与“)”结合起来,将非法序列长度加2,则可得到一个非法子列长度,其间也包含着合法子列,但是由于非法子列”包“在两端,所以长度都算在内,而其他合法子列只需像原来一样处理即可。那么这样的非法子列是不是最短的呢?答案是肯定的,我们将出现的第一个非法子列的左端“)”固定,那么我们找寻到的最近的“(”去与其匹配,如果此序列肯定可以通过排序操作达成合法的话,那么无论从左端还是右端得到的各段非法长度相同。而括号序列检查完毕之后,如果栈内还有剩余的括号,即栈非空,那么则需输出-1表示无法得到,反之,则直接输出非法长度。

    程序核心代码(或伪代码):

    #include <iostream>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    stack<char> st;
    int main() {
        int n,count = 0;
        cin >> n;
        string str;
        cin >> str;
        for(int i = 0; i < n; i++) {
            if(!st.empty() && str[i] == ')' && st.top() == '(') {
                st.pop();
            }
            else if(!st.empty() && str[i] == '(' && st.top() == ')') {
                count += 2;
                st.pop();
            }
            else {
                st.push(str[i]);
            }
        }
        if(st.empty()) {
        	cout << count;
    	}
    	else {
    		cout << "-1";
    	}
    }
    
    

    心得:

    适当的转化问题思路,可以更方便的看清问题和解决问题,需要培养转化意识,来提高自我。

    6.5168

    汉堡包在大街上大摇大摆的走着,看着手机上一道难倒数万人的小学数学题:

    1 + 1 = 0

    1 + 6 = 1

    6 + 6 = 2

    8 + 1 = 2

    8 + 6 = 3

    汉堡包看完之后发现上面这些加法的答案就是看1,6,8中圈圈的个数嘛!

    突然之间,所有大厦上的LED屏幕上的广告全部变成数字1,6,8三个数字的随机闪现。

    现给你一块n*m的LED屏幕,上面有且仅有一个数字(1,6,or 8),请你输出你看见的那个字母。

    输入格式:

    第一行输入两个整数n,m(2<= m, n <= 1000);

    接下来n行,每行由m个数字0和1组成,其中1表示数字1,6,8的组成部分。

    输出格式:

    输出一个整数,代表图形表示的数字。

    输入样例:

    7 7
    0 0 0 0 0 0 0
    0 0 1 1 1 0 0
    0 0 1 0 1 0 0
    0 0 1 1 1 0 0
    0 0 1 0 1 0 0
    0 0 1 1 1 0 0
    0 0 0 0 0 0 0
    
    

    输出样例:

    8
    
    

    分析:

    看到问题首先想到的是针对1、6、8的上面部分来作判断,如果最右侧的第二行为零,则为6,如果中间第二行为0且最右侧第二行为0,则为8,其余为1,但是这样只会过5五个点,经过各种修正和测试提交,才意识到,一横一竖不会只有一行1或一列1来表示,甚至中间的0还不会在正中间(可能)。所以经过思考观察后,可以发现到,1、6、8在每行的1的个数上会有不同种情况,1只有一种,8有两种,6有三种,所以可以通过判断1的个数种类来判断,在输入的时候可以做一个判断,若非零的情况且与之前情况不符,则记录在temp数组里,之后通过判断来解决,若为1,则temp数组里只有一个数据,为6和8,则有五个数据,而6和8的不同就在于第二个和第四个数据是否会不同

    程序核心代码(或伪代码):

    #include <iostream>
    using namespace std;
    int scr[1005][1005];
    int temp[5] = {0};
    int main() {
        int n, m, sign = 0, t = 0;
        cin >> n >> m;
        for(int i = 0; i < n; i++) {
            int sum = 0;
            for(int j = 0; j < m; j++) {
                cin >> scr[i][j];
                sum += scr[i][j];
            }
            if(sum != 0 && sum != t) {
                temp[sign++] = sum;
                t = sum;
            }
        }
        if(temp[1] == 0) {
            cout << '1';
        }
        else if(temp[1] == temp[3]) {
            cout << '8';
        }
        else {
            cout << '6';
        }
    }
    
    

    心得:

    客观观察问题,使用计算机易于判断的方法,来作处理,使得复杂问题转化为简单问题。

  • 相关阅读:
    Delegte的BeginInvoke
    C# socket 实现客户端连续发送数据
    Button的PerformClick()
    Invoke和BeginInvoke
    Application.DoEvents() 处理队列消息,防界面假死
    BackgroundWorker后台线程
    CF773F
    ORM框架,没必要搞那么复杂
    VS Unable to copy file
    Unbuntu auto start program
  • 原文地址:https://www.cnblogs.com/JoshuaYu/p/12640623.html
Copyright © 2020-2023  润新知