程序设计思维与实践 CSP-M2 补题

A - HRZ 的序列

时间限制空间限制
1s64mb
题目描述

相较于咕咕东,瑞神是个起早贪黑的好孩子,今天早上瑞神起得很早,刷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

数据点(上限)tna_i
1,2101010
3,4,510103109
6,7,8,9,10101041015
分析
  • 此题思路简单,主要是要用到set的去重性,通过set中size来分析。其中size<=3时才有可能输出YES。size=3时,中间大小的数不变,小于这部分的数加K,大于这部分的数减K,这就要求2*中间数=最小+最大;size=2时,一个数不变另一个数加/减K即可满足;size=1时,数相等。
  • 其中要十分注意:是输入多组数据!!!,所以每组数据的输入时要进行set的清空!!!痛定思痛!导致爆零的罪魁祸首…
  • 同时要十分注意:ai的最大值为1015,所以数据类型都要用long long!!!set和输入数据处都要定义为long long类型!

C++

#include<iostream>
#include<set>
using namespace std;
long long a1, a2, a3;
set<long long> s;
int main() {
	int t;
	cin >> t;
	while (t--) {
		s.clear();//一定要记得
		int n; cin >> n;
		for (int i = 0; i < n; i++) {
			long long x; cin >> x;
			s.insert(x);
		}
		int index = 1;
		if (s.size() > 3) { cout << "NO" << endl; continue; }
		if (s.size() == 3) {
			for (set<long long>::iterator it = s.begin(); it != s.end(); it++,index++) {
				if (index==1) a1 = *it;
				else if (index==2) a2 = *it;
				else a3 = *it;
			}
			if (a1 + a3 == 2 * a2) 
				cout << "YES" << endl;
			else 
				cout << "NO" << endl;
		}
		else if(s.size()<=2) 
			cout << "YES" << endl;
	}
	return 0;
}

HRZ学英语

时间限制空间限制
1s64mb
题目描述

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

AB??EFGHIJKLMNOPQRSTUVWXYZ

ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABDCEFGHIJKLMNOPQRSTUVWXYZ

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

输入格式

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

输出格式

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

样例输入1

ABC??FGHIJK???OPQR?TUVWXY?

样例输出1

ABCDEFGHIJKLMNOPQRSTUVWXYZ

样例输入2

AABCDEFGHIJKLMNOPQRSTUVW??M

样例输出2

-1

数据点字符串长度
1,2,326
3,4,510000
6,7,8,9,10106
分析

此题可以满足前三个数据点“骗”30分。

  • 此处用到了尺取法,左右端点不断更新。从输入字符串的第一个字符开始检查,当字符为’?'时,如果取到的长度正好为26了则可更新右端点并退出了。bool chars[26]用来记录字符是否出现过了,int location[26];用来记录每个字符所在位置以便出现重复字符时更新左端点。当尺取区间长度为26时则退出(即此时必定是最靠左位置的解)。
  • 注意for (int m = left; m < location[str[i] - ‘A’]; m++)循环中if (str[m] == ‘?’) continue;的添加,因为此时chars[str[m] - ‘A’]中的str[m]可能为’?’,从而导致chars数组出错!

C++

#include<string>
#include<iostream>
using namespace std;
bool chars[26] = { false };
int location[26];
int main()
{
	string str;
	cin >> str;
	int left = 0,right = -1;
	for (int i = 0; i < str.size(); i++)
	{
		if (str[i] == '?')
		{
			if ((i - left + 1) == 26)
			{
				right = i;
				break;
			}
			continue;
		}
		if (chars[str[i] - 'A'] == true)
		{
			for (int m = left; m < location[str[i] - 'A']; m++)
			{
				if (str[m] == '?') continue;//一定要加,否则数组chars[str[m] - 'A']中会报错
				chars[str[m] - 'A'] = false;//重复数第一次出现的位置之前的数全部清除
			}
			left = location[str[i] - 'A'] + 1; //left更新为上一次出现的后面一个位置,避免重复
			location[str[i] - 'A'] = i;//更新位置
		}
		else
		{
			chars[str[i] - 'A'] = true;
			location[str[i] - 'A'] = i;
		}
		if ((i - left + 1) == 26)
		{
			right = i;
			break;
		}
	}
	if (right == -1)
		cout << -1;
	else
	{
		int temp = 0;
		for (int m = left; m <= right; m++)
		{
			if (str[m] != '?')cout << str[m];
			else
			{
				for (int p = temp; p < 26; p++)
				{
					if (!chars[p])
					{
						cout << (char)(p + 'A');
						temp = p + 1;
						break;
					}
				}
			}
		}
	}
	return 0;
}

或者提供的:
在这里插入图片描述

咕咕东的奇妙序列

时间限制空间限制
1s64mb
题目描述

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

输入格式

输入由多行组成。
第一行一个整数q表示有q组询问。(1<=q<=500)
接下来第i+1行表示第i个输入ki,表示询问第ki项数字。(1<=ki<=1018)

输出格式

输出包含q行
第i行输出对询问ki的输出结果。

样例输入

5
1
3
20
38
56

样例输出

1
2
5
2
0

数据点q(上限)k(上限)
1,2,350055
4,5,6100106
7,8,9,105001018
分析
  • 此题思路比较复杂,多次运用到了等差数组等数学知识,又用到了二分法(否则超时),断断续续两三天才完全理清楚。同时这里的数据范围为1018,所以也要用到long long类型。

  • 在第1部分到第9部分,公差为1;第10到第99部分,公差为2;第100到第999部分,公差为3……如下

-------------------m = 1
//第一大段
11部分             公差d=1,首项a0=1,第一段总项数n=9
1 2                          由等差数列求和公式可得sum+= a0 * n + n * (n - 1)*d / 2;
1 2 3
1 2 3 4
1 2 3 4 5
……
1 2 3 4 5 6 7 8 9     结尾项为9=m-1,9部分
-------------------m *= 10
//第二大段
1 2 3 4 5 6 7 8 9 1 010部分             公差d=2,首项a0=11,第二段总项数n=90
1 2 3 4 5 6 7 8 9 1 0 1 1        由等差数列求和公式可得sum
……
1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 1 4 …… 9 8   假如a为在这一部分中的第12项,则sum=10,sum++后为11,y=1,即11中从左数的第一位为答案
1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 1 4 1 5 …… 9 9    结尾项为99=m-1,99部分
-------------------
//第三大段
  • 其中多次调用了getSum(x)函数,来找到此时到第x部分总的项数(x为那一行的最后一个多位数形式数)

C++

#include <cstdio>
#include <iostream>
typedef ll long long 
using namespace std;
int q;
ll a;
ll getSum(ll x)
{//即找到此时到第x部分总的项数(x为那一行的最后一个多位数形式)
	ll m = 1, a0 = 1, d = 0, n = 0;   
	ll sum = 0;
	while (1)
	{
		m *= 10;  //每一段的结尾项后一个项
		d++;    //公差每次增1
		n = m - m / 10;   //每一段的个数 
		if (x > m - 1)
		{
			sum += a0 * n + n * (n - 1)*d / 2;
			a0 = a0 + (n - 1)*d + d + 1;	 // 下一段的首项
		}
		else//x <= m-1时,即x在那一大段的中间位置而不是下一大段中时
		{
			n = x - m / 10 + 1;//找到此时新的n值,x为那一行的尾项
			sum += a0 * n + n * (n - 1)*d / 2;
			break;
		}
	}
	return sum;
}
int main()
{
	scanf("%d", &q);
	while (q--)
	{
		scanf("%lld", &a);
		int left = 0, right = 1000000000, mid = 0, ans;
		ll ssum;
		while (left <= right)
		{//二分找所在段(哪一行也即第几部分) 
			mid = (left + right) >> 1;
			ssum = getSum(mid);
			if (ssum < a)
			{
				ans = mid;//最终ans = (a所在那一行即那一部分的上一行的最后一个数),left = ans+1
				left = mid + 1;
			}
			else
				right = mid - 1;
		}
		a = a - getSum(ans);//a :在 ans+1 段的第a个数字
		ll m = 1, dd = 0, n = 0,y = 0, sum = 0;//其中sum是要找的那个数字所在的多位数前一个多位数
		while (a)
		{//查找所在小分段(那一行即那一部分中的几位数段)
			m *= 10;
			n = m - m / 10;//n为多少个多位数
			dd++;//dd多位数
			if (a > n*dd)
			{
				sum += n;//此时的多位数
				a -= n * dd;
			}
			else
			{
				sum += a / dd;//a所在多位数前一个多位数
				y = a % dd;//y即多位数中的第y位
				break;
			}
		}
		if (y == 0) printf("%lld\n", sum % 10);//多位数的最后末尾数字
		else
		{
			sum ++;
			while (dd != y)//定位到多位数的那一位
			{
				dd--;
				sum /= 10;
			}
			printf("%lld\n", sum % 10);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值