CSP-M2补题(第八周)

T1 HRZ的序列

题目描述

相较于咕咕东,瑞神是个起早贪黑的好孩子,今天早上瑞神起得很早,刷B站时看到了一个序列a,他对这个序列产生了浓厚的兴趣。

他好奇是否存在一个数K,使得一些数加上K,一些数减去K,一些数不变,使得整个序列中所有的数相等。

其中对于序列中的每个位置上的数字,至多只能执行一次加运算或减运算或是对该位置不进行任何操作。

由于瑞神只会刷B站,所以他把这个问题交给了你!

输入格式

输入第一行是一个正整数t表示数据组数。

接下来对于每组数据,输入的第一个正整数n表示序列a的长度,随后一行有n个整数,表示序列a。

输出格式

输出共包含t行,每组数据输出一行。对于每组数据,如果存在这样的K,输出"YES",否则输出“NO”。(输出不包含引号)

样例输入

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

样例输出

NO
NO

在这里插入图片描述
在这里插入图片描述

思路

测验的时候看到这个题并没有很清晰的分析清楚。对每个数据只有三种操作——+K/-K/不变。最后要所有数据相等。
我的思路是对所有数据sort()排序,如果a[1]=a[n],那么原数据全相等;若不等,即a[1]<a[n],最后要a[1]=a[n],有三种情况——①a[1]+K=a[n],最终所有数据等于a[n],②a[n]-K=a[1],最终所有数据等于a[1],③a[1]+K=a[n]-K,最终所有数据等于(a[1]+a[n])/2。

  • 对情况①和情况②,原数据为a[1]或a[n]
  • 对情况③,原数据为可为a[1]或a[n]或(a[1]+a[n])/2

总结

如果一个序列满足以下任意性质,则一定存在这样的K:

1.序列中不同的数小于三个。
2.序列中不同的数只有三个,并且最小的和最大的数的和等于另一个数的两倍。

  • 可考虑set存数(自带去重和排序)
  • 新建一个数组存储数列a中不同的数

注意

对long long 数据加减也要用long long存
判断==别打成=赋值
long long数据scanf输入一定一定记得lld!!!

代码

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

int t,n;
long long a[10100];

int main() {
	scanf("%d",&t);
	while(t--) {
		scanf("%d",&n);
		bool flag=1;
		for(int i=1;i<=n;++i) scanf("%lld",a+i);//注意long long型数据格式lld!!!
		sort(a+1,a+n+1);
		if(a[1]==a[n]) {
			printf("YES\n");
			continue;
		}else {
			for(int i=1;i<=n;++i) {
				if(a[i]!=a[1]&&a[i]!=a[n]) {
					flag=0;
					break;
				}
			}
			if(flag==0) {
				if((a[1]+a[n])%2==0) {
					long long temp=(a[1]+a[n])>>1;
					flag=1;
					for(int i=1;i<=n;++i) {
						if(a[i]!=a[1]&&a[i]!=a[n]&&a[i]!=temp) {
							flag=0;
							break;
						}
					}
				}
			}
			if(flag) printf("YES\n");
			else printf("NO\n");
		}
	}
	return 0;
}

T2 HRZ学英语

题目描述

瑞神今年大三了,他在寒假学会了英文的26个字母,所以他很兴奋!

于是他让他的朋友TT考考他,TT想到了一个考瑞神的好问题:给定一个字符串,从里面寻找连续的26个大写字母并输出!

但是转念一想,这样太便宜瑞神了,所以他加大了难度:现在给定一个字符串,字符串中包括26个大写字母和特殊字符’?’,特殊字符’?'可以代表任何一个大写字母。

现在TT问你是否存在一个位置连续的且由26个大写字母组成的子串,在这个子串中每个字母出现且仅出现一次,如果存在,请输出从左侧算起的第一个出现的符合要求的子串,并且要求,如果有多组解同时符合位置最靠左,则输出字典序最小的那个解!如果不存在,输出-1!

这下HRZ蒙圈了,他刚学会26个字母,这对他来说太难了,所以他来求助你,请你帮他解决这个问题,报酬是可以帮你打守望先锋。

说明:字典序 先按照第一个字母,以 A、B、C……Z 的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,SIGH 和 SIGHT),那么把短者排在前。例如

AB??EFGHIJKLMNOPQRSTUVWXYZ

ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABDCEFGHIJKLMNOPQRSTUVWXYZ
(注:CD与DC的区别)

上面两种填法,都可以构成26个字母,但是我们要求字典序最小,只能取前者。
注意,题目要求的是第一个出现的,字典序最小的!

输入格式

输入只有一行,一个符合题目描述的字符串。

输出格式

输出只有一行,如果存在这样的子串,请输出,否则输出-1

样例输入1

ABC??FGHIJK???OPQR?TUVWXY?

样例输出1

ABCDEFGHIJKLMNOPQRSTUVWXYZ

样例输入2

AABCDEFGHIJKLMNOPQRSTUVW??M

样例输出2

-1

在这里插入图片描述
在这里插入图片描述

思路

整体的思路是从前往后,类似于“毛毛虫法”,每次截取初始字符串的26个字符区间,再对每一个A-Z字母计数(‘?’不计),存入cnt数组,检查cnt数组各字母的计数:若都为不大于1,满足题意(剩余未计数’?‘可完全替代计数为0的字母);若有大于1的,不符题意,区间后移。
找到后,从前往后,用未出现的字母(即cnt为0的字母)在该区间内寻找’?'的位置,赋值替代即可。
替代结束整体输出返回。

注意

后移时,注意前一个区间的第一个字母cnt–,后一个区间最后一个字母cnt++(非’?'的条件下)。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>

using namespace std;

string s;
int cnt[27];

int main() {
	cin>>s;
	for(int i=0;i<26;++i) {
		if(s[i]!='?') cnt[s[i]-64]++;
	}
	string temp(s,0,26);
	int i;
	for(i=0;i+26<=s.size();++i) {
		if(i>0) {
			if(temp[0]!='?') cnt[temp[0]-64]--;
			temp.replace(0,26,s,i,26);//后移
			if(temp[25]!='?') cnt[temp[25]-64]++;
		}
		bool flag=1;
		for(int j=1;j<=26;++j) {
			if(cnt[j]>1) {
				flag=0;
				break;
			}
		}
		if(flag) {
			for(int j=1;j<=26;++j) {
				if(cnt[j]==0) {
					int index=temp.find('?');
					temp[index]=char(j+64);
				}
			}
			cout<<temp<<endl;
			return 0;
		}
	}
	if(i+26>s.size()) printf("-1\n");
	return 0;
}

T4 咕咕东的奇妙序列

题目描述

咕咕东 正在上可怕的复变函数,但对于稳拿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 项数字是多少!但是她睡醒之后发现老师讲的东西已经听不懂了,因此她把这个任务交给了你。

输入格式

在这里插入图片描述

输出格式

在这里插入图片描述

样例输入

5
1
3
20
38
56

样例输出

1
2
5
2
0

在这里插入图片描述
在这里插入图片描述

思路

注意到对于每个数量级内各部分的长度构成等差序列,例:1-9,差d=1;10-99,差d=2;100-999,差d=3;1000-9999,差d=4…
对于第k项我们可以先找到它属于哪个数量级,再找到其属于的部分ans(对应于代码中的find1()找段部分)。找到其所属部分后,更新k=k-该部分前的总长(k-find1(ans-1)),在该部分内找k属于哪个字ans,过程与前面类似(对应于代码中find2()找字部分),找到后,更新k=k-该部分前面所有字长(k-find2(ans-1)),至此,k就是在这个字中的位置,to_string(ans)该字转字符串,输出k-1处字符对应数即可。

注意

  • 找段找字中的a和c的更新是不同的,a是原序列中各数量级的等差数列首项长,c是单字中各数量级每个数的长度
  • 本题采用二分查找的方式查找,可降低复杂度,每次找到k所属字/段,即第一个长度>=k的ans
  • 每次更新:k-=find(ans)

代码

#include<iostream>
#include<cstdio>
#include<string>

using namespace std;

long long q,k,l,r,mid,ans;

long long find1(long long x) {
	long long pow=10,sum=0,a=1,n=0,d=1;
	while(x>pow-1) {
		n=pow-pow/10;//项数,1-9,10-99,100-999,... 
		sum+=a*n+n*(n-1)*d/2;//等差数列求和公式 
		
		a+=n*d+1;//下一个等差数列的第一项 
		pow*=10;d++;//更新幂次和差d 
	}
	n=x-pow/10+1;//剩余项数 
	sum+=a*n+n*(n-1)*d/2;
	return sum;
} 

long long find2(long long x) {
	long long pow=10,sum=0,n=0,c=1;
	while(x>pow-1) {
		n=pow-pow/10;
		sum+=n*c;
		pow*=10;c++;
	}
	n=x-pow/10+1;
	sum+=n*c;
	return sum;
}

int main() {
	int num=0;
	scanf("%lld",&q);
	while(q--) {
		scanf("%lld",&k);
		l=0;r=1e9;
		while(l<=r) {
			//寻找段
			mid=(l+r)/2;
			if(find1(mid)>=k) {
				//第一个大于 
				ans=mid;r=mid-1; 
			}else l=mid+1; 
		}
		k-=find1(ans-1);//减去前面所有段的长度
		l=0;r=ans+1;
		while(l<=r) {
			//寻找具体的字
			mid=(r+l)/2;
			if(find2(mid)>=k) {
				ans=mid;r=mid-1;
			}else l=mid+1; 
		}
		k-=find2(ans-1);//减去前面所有字的长度
		
		string s=to_string(ans);
		num=s[k-1]-'0';
		printf("%d\n",num); 
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值