【CSP - M2】A - HRZ的序列、B - HRZ学英语、C - 咕咕东的奇妙序列

A - HRZ的序列

题意:

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

Input

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

Output

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

Sample Input

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

Sample Output

NO
NO

思路做法:

很容易想到当输入的数字中有超过3个数字不同时就不存在数字 K K K使得原问题成立了。
因此这作为一个判断条件,可以用set存储数字,当set的大小大于等于4时直接输出“NO”结束。
而set的大小为1或2时,很明显存在数字 K K K使原问题有解,这时直接输出“YES”结束。
当set的大小为3时,可以把数字排序,若 n u m [ 0 ] + n u m [ 2 ] = = n u m [ 1 ] num[0]+num[2]==num[1] num[0]+num[2]==num[1]说明原问题有解,否则不存在相应的 K K K,无解。

总结:

刚看题目可能会想复杂,但是思考一下就想到可以用set处理。

代码:

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

const int N = 1e4+5;
long long a[N];
set<long long> cnt;

int main(){
	int t; scanf("%d", &t);
	while(t--){
		cnt.clear();
		int n; scanf("%d", &n);
		for(int i = 0; i < n; ++i){
			scanf("%lld", &a[i]);
			cnt.insert(a[i]);
		}
		if(cnt.size() > 3){
			printf("NO");
		}else{
			if(cnt.size() == 3){
				long long num[3]; int len = 0;
				for(set<long long>::iterator it=cnt.begin(); it!=cnt.end(); it++){
					num[len++] = *it;
				}
				sort(num, num+3);
				if(num[0]+num[2] == 2*num[1]){
					printf("YES");
				}else{
					printf("NO");
				}
			}else{
				printf("YES");
			}
		}
		printf("\n");
	}
	return 0;
} 

B - HRZ学英语

题意:

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

AB??EFGHIJKLMNOPQRSTUVWXYZ

ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABDCEFGHIJKLMNOPQRSTUVWXYZ

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

Input

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

Output

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

Sample Input

ABC??FGHIJK???OPQR?TUVWXY?

AABCDEFGHIJKLMNOPQRSTUVW??M

Sample Output

ABCDEFGHIJKLMNOPQRSTUVWXYZ

-1

思路做法:

不是很难的一道模拟题,从头到尾便历一遍字符串,用map记录字母的个数(不重复)和’?'的个数,第一次找到符合条件的按相应规则(最小字典序)输出即可。

总结:

之所以没全过是把尺取用成了二分。我也不知道当时怎么想的。

代码:

#include <string.h>
#include <map>
#include <stdio.h>
using namespace std;
map<char, int> cnt;
const int N = 1e6+5;
char str[N];

bool isOk(){
	int num = 0;
	for(int i = 0; i < 26; ++i){
		char c = i+'A';
		if(cnt[c] > 1) return false;  // 出现次数大于1 
	}
	return true;
}

int main(){
	scanf("%s", str);
	int len = strlen(str);
	int le = 0, ri = len - 26;
	for(int i = 0; i < 26; ++i){
		cnt[str[i]]++;
	}
	while(le <= ri){
		if(isOk()){
			int z[26], num = 0;
			for(int i = 0; i < 26; ++i) z[i] = 0;
			for(int i = 0; i < 26; ++i){
				char c = i + 'A';
				if(cnt[c] == 0) z[num++] = i;  // 未出现的字母 
			}
			num = 0;
			for(int i = le; i < le + 26; ++i){
				char c = str[i];
				if(c == '?') printf("%c", z[num++] + 'A');
				else printf("%c", c);
			}
			printf("\n");
			return 0;
		}else{
			if(le < ri){
				--cnt[str[le]];
				++cnt[str[le+26]];	
			}
			++le;
		}
	}
	printf("-1\n");
	return 0;
}

C - 咕咕东的奇妙序列

题意:

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

Input

输入由多行组成。
第一行一个整数 q q q表示有 q q q组询问 ( 1 < = q < = 500 ) (1<=q<=500) (1<=q<=500)
接下来第 i + 1 i+1 i+1行表示第 i i i个输入 k i k_i ki,表示询问第 k i k_i ki项数字. ( 1 < = k i < = 1 0 18 ) (1<=k_i<=10^{18}) (1<=ki<=1018)

Output

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

Sample Input

5
1
3
20
38
56

Sample Output

1
2
5
2
0

思路做法:

根据等差数列的性质计算。虽然看起来与等差数列不同,但是仍然是等差数列。其中数列的通项为1,2,……x的长度,数列和为长度相加即总长度。
main函数中首先对于输入计算数字ans,该数字代表等差数列求和中最靠近k但又不超过的索引值。为提高速度,用二分法寻找这个索引,题目的k上界为1e18,二分的上界去其开根,为1e9,并用函数calcu_sum计算到索引x为止题目中数列所有数字的数量和。由于这个数列的通项每隔10,100,……变化,因此可以每次循环乘10,改变通项(首项a和公差d)。得到ans后说明k就在ans所属的子序列1,2,……ans中,用仍然用二分解相应的新ans值,calcu_num函数与calcu_sum函数差不多,只是它是计算数列的通项而不是数列的和。通过二分得到接近但不超过k值的ans,相减得到达k还需要的索引值,用栈取出相应索引值的数字即为答案

总结:

中间推导了等差数列通项与求和的公式,在1-9,10-99,……可以通过递推计算任一数的通项和前n项和,但主要还是main函数里的二分思想最为重要,不然列表或挨个算时间肯定会超。

代码:

#include <stdio.h>
#include <math.h>
#include <string.h> 
#define ll long long
#include <stack>
using namespace std;

// 1 1 2 1 2 3 1 2 3 4 1 2 3 4 5
ll calcu_sum(ll x){  // 求总和 
	// 等差数列 n*a+n*(n-1)/2*d  返回数列求和 
	// len(x) 1 ~ x 的长度 相当于数列通项 
	// n 到 10^(cnt-1) 的个数  x-10^(cnt-1)+1 
	// 首项 a = len(10^(cnt-1)-1)+d  公差 d = cnt 位数 
	// sum = n*(len(10^(cnt-1)-1)+cnt)+n*(n-1)/2*cnt
	//     = n*len(10^(cnt-1)-1)+2*n*cnt/2+n*(n-1)/2*cnt
	//     = n*len(10^(cnt-1)-1)+n*(n+1)/2*cnt
	// 总sum += n*len(10^(cnt-1)-1)+n*(n+1)/2*cnt
	// 如101 cnt = 3  a = len(99)+4  n = 101-100+1 = 2
	// len(10^(cnt-1)-1)
	// len(99) 是否能由 len(9)算出? 
	// len(99) = len(9)+(10, 11, ~ ,99) 
	// a = len(10^(cnt-1)-1)  d = cnt
	// n = 10^cnt-1-10^(cnt-1)+1 = 10^cnt - 10^(cnt-1)
	// len(10^cnt-1) = a + n*d
	//               = len(10^(cnt-1)-1)+n*cnt
	ll cnt = 0, len = 0, sum = 0, n = 1, m;  // 保险起见全用ll 
	while(true){
		n *= 10; cnt++;
		// 1个1个累加太慢 先按10,100……10^cnt递增 
		if(n - 1 < x){  // 9 99 999 < x
			m = n - n / 10;  // 按10^cnt - 10^(cnt-1)加满 
			sum += m * len + m * (m+1) / 2 * cnt;
			len += m * cnt;
		}else{  // 9 99 999 >= x 可以停止了 
			m = x - n / 10 + 1;  // 记住 x-10^(cnt-1)+1 要+1 
			sum += m * len + m * (m+1) / 2 * cnt;
			len += m * cnt;
			break;
		}
	}
	return sum;
}

ll calcu_num(ll x){  // 与calcu_sum类似,但是计算出长度len即可 
	ll cnt = 0, len = 0, sum = 0, n = 1, m;
	while(true){
		n *= 10; cnt++;
		// 1个1个累加太慢 先按10,100……10^cnt递增 
		if(n - 1 < x){  // 9 99 999 < x
			m = n - n / 10;  // 按10^cnt - 10^(cnt-1)加满 
			len += m * cnt;
		}else{  // 9 99 999 >= x 可以停止了 
			m = x - n / 10 + 1;  // 记住 x-10^(cnt-1)+1 要+1 
			len += m * cnt;
			break;
		}
	}
	return len;
}

int main(){
	int q; scanf("%d", &q);
	while(q--){
		ll k; scanf("%lld", &k);
		ll le = 0, ri = 1e9, mid, ans;  // 取1e18开根作为上限
		while(le <= ri){
			mid = (le + ri) >> 1;  // 线性太慢,用二分查找
			if(calcu_sum(mid) < k){
				le = mid + 1; ans = mid;  // 找到最靠近但又不超过的 
			}else{
				ri = mid - 1;
			}
		}
//		printf("ans1 = %d\n", ans);
		// k 在(ans,ans+1)里 
		k -= calcu_sum(ans);  // 计算多余的数值 在ans+1中寻找 
		le = 0; ri = ans+1;
		while(le <= ri){  // 仍然使用二分 
			mid = (le + ri) >> 1;
			if(calcu_num(mid) < k){
				le = mid + 1; ans = mid;  // 靠近但不超过 
			}else{
				ri = mid - 1;
			}
		}
//		printf("ans2 = %d\n", ans);
		k -= calcu_num(ans);  // 在ans+1中多余的项 
		ans += 1; k -= 1;
		stack<int> s;
		while(ans){
			s.push(ans % 10);
			ans /= 10;
		}
		while(k--) s.pop();
		printf("%d\n", s.top());
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

容嬷嬷当年一枝花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值