【20200401程序设计思维与实践 CSP-M2&补题】


A - HRZ 的序列

题意

相较于咕咕东,瑞神是个起早贪黑的好孩子,今天早上瑞神起得很早,刷B站时看到了一个序列a,他对这个序列产生了浓厚的兴趣,他好奇是否存在一个数K,使得一些数加上K,一些数减去K,一些数不变,使得整个序列中所有的数相等,其中对于序列中的每个位置上的数字,至多只能执行一次加运算或减运算或是对该位置不进行任何操作。由于瑞神只会刷B站,所以他把这个问题交给了你!


思路

本题采取的做法是统计出现数字的种数。

观察后发现:当序列中数字种类大于等于4时必不存在满足题意的K;当数字种类小于等于2时必存在满足题意的K;当数字种类恰好为3时,仅当三数成等差数列时存在满足题意的K。

接下来统计数字种类:将原始序列排序,扫描序列中的数字种类。因为序列有序,所以每当当前元素与上一元素值不相等时,序列中数字种类增加1,同时记下该元素的值;当数字种类超过4时,即可直接中断扫描。扫描结束后,根据上述判断方法即可得知是否存在K。


总结

本题观察思考后即可找到简单的解法,难度不高。

debug:注意数据范围,小心int溢出。


代码

#include<stdio.h>
#include<algorithm>
using namespace std;

int main(){
	int t;
	scanf("%d",&t);
	for(int i=1;i<=t;i++){
		int n;
		scanf("%d",&n);
		long long* a=new long long[n+10];//注意数据范围 
		for(int j=0;j<n;j++)scanf("%lld",&a[j]);
		sort(a,a+n);//预处理 
		int cot=1;
		long long rec[10];
		rec[1]=a[0];
		for(int j=1;j<n;j++){//扫描序列中的数字种类 
			if(a[j]!=a[j-1])rec[++cot]=a[j];
			if(cot>=4)break;
		}
		if(cot>=4)printf("NO\n");
		else if(cot<=2)printf("YES\n");
		else {//当cot==3时,判断等差数列 
			int a1=rec[2]-rec[1];
			int a2=rec[3]-rec[2];
			if(a1==a2)printf("YES\n");
			else printf("NO\n");
		}
		delete a;
	}
}

B - HRZ 学英语

题意

瑞神今年大三了,他在寒假学会了英文的26个字母,所以他很兴奋!于是他让他的朋友TT考考他,TT想到了一个考瑞神的好问题:给定一个字符串,从里面寻找连续的26个大写字母并输出!但是转念一想,这样太便宜瑞神了,所以他加大了难度:现在给定一个字符串,字符串中包括26个大写字母和特殊字符’?’,特殊字符’?'可以代表任何一个大写字母。现在TT问你是否存在一个位置连续的且由26个大写字母组成的子串,在这个子串中每个字母出现且仅出现一次,如果存在,请输出从左侧算起的第一个出现的符合要求的子串,并且要求,如果有多组解同时符合位置最靠左,则输出字典序最小的那个解!如果不存在,输出-1! 这下HRZ蒙圈了,他刚学会26个字母,这对他来说太难了,所以他来求助你,请你帮他解决这个问题,报酬是可以帮你打守望先锋。
说明:字典序 先按照第一个字母,以 A、B、C……Z 的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,SIGH 和 SIGHT),那么把短者排在前。


思路

本题直接进行暴力检索。

从头开始扫描字符串,以此保证所求子串为第一个出现。对于每一个字符,检查它和后续25个字符是否不重复(包含’?’),为此,在每次检查时准备一个重置好的长度为26的辅助数组来记录出现情况。若通过检查,则再次利用该辅助数组为’?‘赋值:从头扫描辅助数组,每有一个位置为空(该字母未出现)则为下一个’?'赋该值,以此保证了字典序最小。

预先准备一个判断标记,用于在没找到的情况下判断输出-1。


代码

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

string str;

int jdg[30];

void clean(){
	for(int i=0;i<=25;i++)jdg[i]=0;
}

int main(){
	cin>>str;
	int J=0;//标记是否存在 
	int len=str.length();
	for(int i=0;i<len-25;i++){//扫描字符串, 
		clean();//重置 
		int cot=0;
		while(cot<26){//对每个字符,检查其与后续共26个字符是否恰为字母表出现一次 
			if(str[i+cot]=='?'){
				cot++;
				continue;
			}
			if(jdg[str[i+cot]-'A']!=0)break;
			jdg[str[i+cot]-'A']=1;
			cot++;
		}
		if(cot<26)continue;//不连续,循环继续 
		int tag=0;
		for(int j=0;j<26;j++){//按字母表顺序,为'?'赋值以实现字典序最小 
			if(str[i+j]!='?')cout<<str[i+j];
			else{
				while(jdg[tag])tag++;
				jdg[tag]=1;
				char c='A'+tag;
				cout<<c;
			}
		}
		J=1;
		break;
	}
	if(!J)cout<<-1;
}

C - 咕咕东的奇妙序列

题意

咕咕东 正在上可怕的复变函数,但对于稳拿A Plus的 咕咕东 来说,她早已不再听课,此时她在睡梦中突然想到了一个奇怪的无限序列:112123123412345 …这个序列由连续正整数组成的若干部分构成,其中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所有数字,第i部分总是包含1至i之间的所有数字。所以,这个序列的前56项会是11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是5,第38项是2,第56项是0。咕咕东 现在想知道第 k 项数字是多少!但是她睡醒之后发现老师讲的东西已经听不懂了,因此她把这个任务交给了你。


思路

一开始没有考虑到过高的时间复杂度和空间开销,考虑的是动态维护两个数组,直接采用二分答案来解。两个数组L(i)与S(i),L(i)储存“数到真值i的序列的总长度”,S(i)储存L(j)的前i项和,通过函数调用直接取现有值或现场计算,通过两次二分答案,先后确定所要求的que在数组S中的位置i、que-S(i)在数组L中的位置j,从而得到位置K上的数码。但是实际操作后发现,复杂度为O(N)的递归无法避免(N高达1e18,无法接受),并且2e18*sizeof(long long)的空间开销溢出。在反复微调后认清了行不通的事实,只能重新换思路。

随后,依然是分段的思想,但这次考虑分更大的块来保证时空效率。最终决定如图分割整个答案范围,仍然使用上文的数列概念,每一个小梯形为整个“奇妙序列”中L(1ei)~L(1e(i+1)-1)的部分。

预估数据范围,发现实际上n的最大值为9即可满足题目要求的数据范围。在程序开始运行时计算四个数组B、K、S、M,时间开销为几乎可以忽略的常数。这样,对于输入的每一个查询que,只要一次1:9的遍历,找到所处的梯形,即第find+1个。

接着,寻找que所属的梯形中的具体某一序列(图中表示成红线)。此处用到二分答案。设计了函数sum(),用数组B与等差数列求和公式可以求出被红线分割出的左侧梯形的大小,将sum()作为二分答案的指标,最终确定出que所属子序列(即红线)的位置,即find2。

再将该子序列进行切割,通过累减确定que所属真值有几位、que在真值的第几位、que所属真值是多少,最终得到que的数码。


Debug

最初思路在修改之前,反复尝试优化时空效率无果,最终才意识到要改变做法。

经过一些小修改后新思路顺利实现,但发现数据逼近边界时会WA。尝试了使用枚举和二分搜索来寻找开始发生错误的地方,因为数据过大无果。考虑到已经有相当数量的小数据通过,逻辑问题应该不大,开始思考从边界溢出开始考虑问题。

首先将注意力锁定在了stl的pow函数上,考虑到精度问题,尝试用手打指数运算代替stl函数,输出答案的确发生了变化,但依然不正确。

最终发现问题出在二分答案的起始状态上。因为公式书写有误,在边界测试数据上,本该恰好卡在表示范围内的幂值溢出,导致答案错误。

本题耗费了大量时间调试和debug,感谢学长和同学不厌其烦地指点。


代码

#include<iostream>
#include<math.h>
#define MAX 9
using namespace std;

long long Q;
long long que;
long long B[MAX+5];
long long K[MAX+5];
long long S[MAX+5];
long long M[MAX+5];

long long Power(long long a,long long x){
	long long ans=1;
	while(x--)ans*=a;
	return ans;
}

long long sum(long long n,long long i){
	return i*B[n]+i*(i+1)/2*(n+1);
}

int main(){
	B[0]=K[0]=S[0]=M[0]=0;
	for(long long i=1;i<=MAX;i++){
		long long num=9*Power(10,i-1);
		B[i]=B[i-1]+num*i;
		if(i==1)K[i]=(1+num)/2*num*i;
		else K[i]=num/2*(1+num)*i;
		S[i]=K[i]+B[i-1]*num;
		M[i]=M[i-1]+S[i];
		}
	
	cin>>Q;
	for(long long i=1;i<=Q;i++){
		cin>>que;
		long long find1;
		for(find1=MAX;find1>=0;find1--){
			if(M[find1]<que)break;
		}
		que-=M[find1];
		
		long long l,r,m,find2;
		l=0;
		r=9*Power(10,find1)-1;
		find2=1;
		while(l<=r){
			m=(l+r)>>1;
			if(sum(find1,m)<que){
				find2=m;
				l=m+1;
			}
			else if(sum(find1,m)>=que){
				r=m-1;
			}
		}
		que-=sum(find1,find2);
		
		long long find3;
		for(find3=find1;find3>=0;find3--){
			if(B[find3]<que)break;
		}
		que-=B[find3];
		
		long long find4,find5;
		find4=que/(find3+1);
		find5=que%(find3+1);
		if(find5==0){
			find4--;
			find5=find3+1;
		}
		long long start=1;
		for(long long j=1;j<=find3;j++)start*=10;
		start=start+find4;
		start/=Power(10,find3+1-find5);
		start%=10;
		cout<<start<<endl; 
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值