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

本文解析了三道算法竞赛题目,包括A-HRZ序列、B-HRZ学英语和C-咕咕东的奇妙序列。分别介绍了如何判断序列是否能通过加减运算使所有元素相等、寻找包含26个不同字母的连续子串,以及确定奇妙序列的第k项。提供了详细的解题思路和代码实现。
摘要由CSDN通过智能技术生成

A - HRZ 的序列

题意

给定一个序列a,问是否存在一个数K,使得a中的每一个数 + K 、 − K 或 不 变 +K、-K或不变 +KK,最终a中每一个数都相等。

解题思路

我们很容易知道,这样的序列只有2种可能:

  1. 序列中一共有2个数,K = 这两个数的平均值。
  2. 序列中有3个数,且其中一个数是另外两个数的平均数。

我们对a进行排序,之后判断中间的数是否为最大、最小或最大最小的平均数即可。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
long long a[10010];
int main(){
	int t;
	cin >> t;
	while(t--){
		int n;
		cin >> n;
		for(int i = 0; i < n; i++){
			cin >> a[i];
		}
		sort(a, a + n);
		long long k = (a[n-1] - a[0])/2;
		bool flag = true;
		for(int i = 0; i < n; i++){
			if(a[i] != a[0] && a[i] != a[n-1]){
				if(((a[n-1] - a[0])%2) || (a[i] != a[0]+ k))	
					flag = false;
					break;
			}
		}
		if(flag) cout << "YES\n";
		else cout << "NO\n";
	}
	return 0;
}

B - HRZ 学英语

题意

给定一个字符串,字符串中包含26个大写字母和 ′ ? ′ '?' ?,问是否存在一个长度为26的连续子串,包含26个不同字母,其中 ′ ? ′ '?' ?可以代替任意字母。若存在,则输出符合要求的字典序最小的子串。

解题思路

我们可以对字符串中从第一个字符到第l-25个(l为字符串长度)字符,以当前字符为首的长度为26的子串进行逐一判断,可以使用数组来记录字串中每一个字母的数量,用一个变量来记录 ′ ? ′ '?' ?的数量,对每一个子串进行判断,未出现的字母数量若与 ′ ? ′ '?' ?数量相等,则进行输出子串。
对于子串的最小字典序,首先将固定的字母填入答案串中,然后从末尾开始,将未填入的字母中最大的填入最右端的问号中,直至填充完成。(我为什么不从头开始填充问号= =,可能考场上脑子瓦特了)

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
char str[1000005];
int cnt[26];
int num;
int ansi;
char ans[26];
bool flag_ans[26];
bool flag = false;
int main(){
	for(int i = 0; i < 26; ++i){
		ans[i] = '?';
	}
	scanf("%s",str);
	int len = strlen(str);
	for(int i = 0; i < 26; i++){
		if(str[i] != '?')
			cnt[str[i] - 'A']++;
		else{
			num++;
		}
	}
	int num1 = 0;
	for(int i = 0; i < 26; i++){
		if(cnt[i] == 0){
			num1++;
		}
	}
	if(num1 == num){
		flag = true;
		goto out;
	}
	for(int i = 26; i < len; i++){
		if(str[i] == str[i-26])
			continue;
		else{
			if(str[i] != '?')
				cnt[str[i] - 'A']++;
			else
				num++;
			if(str[i-26] != '?')
				cnt[str[i-26] - 'A']--;
			else
				num--;
		}
		num1 = 0;
		for(int i = 0; i < 26; i++){
			if(cnt[i] == 0){
				num1++;
			}
		}
		if(num1 == num){
			flag = true;
			ansi = i - 25;
			break;
		}
	}
	out:
	if(flag){
		for(int i = ansi; i < ansi+26; i++){
			if(str[i] != '?'){
				ans[i - ansi] = str[i];
				flag_ans[str[i]-'A'] = true;
			}
		}
		for(int i = 25; i >= 0; i--){
			if(ans[i] == '?'){
				for(int j = 25; j >= 0; j--){
					if(flag_ans[j] == false){
						flag_ans[j] = true;
						ans[i] = 'A' + j;
						break;
					}
				}
			}
		}
		cout << ans;
	}else
		cout << -1;
	return 0;
}

C - 咕咕东的奇妙序列

题意

有一个序列:112123123412345123456… 这个序列由连续正整数组成的若干部分构成,其中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所有数字,第i部分总是包含1至i之间的所有数字。
问数列的第k项是几。

解题思路

首先在考场上我想到的是前缀和的做法,sum[i]表示第i组及第i组之前的位数之和,首先求出sum[i],然后对于每一个输入的k,先二分法计算出第k项位于第几组,再在求出它在这个组的第几位即可。但是这个方法有一个问题就是数据规模过大达到10^18,求取前缀和的时间复杂度为O(N),空间占用也大,既超时,又超内存,只能拿60%的分数。(最后差一点调出来,一分没拿,实际上2分钟交一个只判断56位的也有30分,下次要改一改策略)
我想出来的正解是基于前缀和的方法优化的,前缀和没有必要一次性求出并存储,可以用优化的算法非常快速的动态求取,需要用的时候再求。我们知道第1-9组,每一组的位数+1,第10到99组,每一组位数+2,第100到999组,每一组位数+3,以此类推,再根据等差数列求和公式,我们能够在log的时间内求出需要的sum[i]并返回。
计算出在第几组后可以使用类似的方法计算出第k项在这组的哪个数中,最后直接输出该数的对应位即可(这里可以使用to_string函数来简化操作)。

代码

#include <iostream>
#include <cmath>
#include <string>
using namespace std;
long long lg(long long a){
    long long k = 0;
    while(a / 10){
        k++;
        a /= 10;
    }
    return k;
}
long long power(long long a){
    if(a == 0) return 0;
    long long sum1 = 1;
    while(a--){
        sum1 *= 10;
    }
    return sum1;
}
long long getSum(long long x){
    long long ans = 0, a = 0, b = 1;
    int time = lg(x)+1;
    while(true){
        if(b == time) break;
        ans = ans + (a + a + b * (power(b)-1 - power(b-1)))*(power(b) - power(b-1)) / 2;
        a = a + b * (power(b)-1 - power(b-1)) + b + 1;
        b++;
    }
    ans = ans + (a + a + b * (x - power(b-1)))*(x - power(b-1) + 1) / 2;
    return ans;
}
long long solve(long long k)
{
    long long i;
    int l = 1, r = 1000000000;
    while(l <= r){
        int mid = (l + r) >> 1;
        if(getSum(mid) >= k){
            i = mid;
            r = mid - 1;
        }
        else l = mid + 1;
    }
    long long cur = k - getSum(i-1);
    long long mark = 0,temp;
    long long j;
    for(j = 0; ; j++){
        temp = mark;
        if(j == 0)
            mark += 9;
        else
            mark += (j + 1)*(power(j+1)-power(j));
        if(mark >= cur)
            break;
    }
    long long t = (cur - temp)%(j+1);
    long long ans = (cur - temp - 1)/(j+1) + power(j);
    if(j == 0)
        ans++;
    if(t!=0)
        cout << to_string(ans)[t-1] << endl;
    else 
        cout << to_string(ans)[j] << endl;
}
int main(){
    long long q, k;
    cin >> q;
    while(q--){
        cin >> k;
        solve(k);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值