程序设计思维与实践 Week8 CSP模测

A HRZ的序列

1. 题目大意

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

2. 思路历程

  • 对于满足要求的序列,序列中只能有三个不同的数,a - k, a, a + k,因此只对整个序列不同的数字个数进行考虑即可。
  • 对序列进行排序,遍历即可计数cnt
  1. cnt > 3,则肯定不符合要求
  2. cnt = 3,判断三个数是否满足等差数列,若满足则符合要求
  3. cnt = 1cnt = 2,肯定符合要求

3. 反思

这题我的思路和代码都是正确的,唯独开错了数据范围(补题只改了maxn就通过了),谁能想到我会把104当成四位数呢(唉
一定要注意数据范围啊!

4. 代码

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 11111;

int t, n, cnt = 0;
long long a[maxn], now, dif[maxn];

int main()
{
    cin >> t;
    for (int i = 0; i < t; i++)
    {
        cin >> n;
        for (int j = 0; j < n; j++)
            cin >> a[j];
        
        sort(a, a + n);
        now = a[0];
        dif[0] = a[0];
        cnt = 1;
        for (int j = 1; j < n; j++)
        {
            if (a[j] != now)
            {
                dif[cnt] = a[j];
                now = a[j];
                cnt++;
            }
        }
        
        if ((cnt == 3 && dif[1] - dif[0] == dif[2] - dif[1]) || cnt == 2 || cnt == 1)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }
    return 0;
}

B HRZ学英语

1. 题目大意

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

AB??EFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABDCEFGHIJKLMNOPQRSTUVWXYZ

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

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

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

样例:

ABC??FGHIJK???OPQR?TUVWXY?
ABCDEFGHIJKLMNOPQRSTUVWXYZ

2. 思路历程

  • 考虑尺取法,左右指针的位置相差25,指针从序列起始位置开始,每次l++, r++考虑下一个情况。
  • 对于每一段26个字符的序列,计算其中每个字母的个数和问号的个数(最后问号可以换成这部分序列中任何没出现的字母)
  1. 若未出现的字母数量大于问号的数量,则这一段肯定不符合要求(即使把所有问号换成字母也有字母不存在)
  2. 若未出现的字母数量小于等于问号的数量,则对这一段的问号进行替换:在遍历序列时就记录问号的位置,之后对于每个位置进行替换

3. 反思

模测时想到了尺取法,但是没有想清楚左右指针的位置和移动方式,当时非常困扰,就先奔去T3了,后来因为时间不够直接没写出来。
感觉自己有点死板于之前讲的尺取法,不知道如何变通,其实这里的左右指针是同时移动的,我按照之前的尺取法考虑还把情况复杂化了。
一定要多思考啊!方法只体现思想,它会有很多不同的实现形式,要根据具体问题具体分析。
CSP题难度严格递增,千万不能因为前一题比较费时间就跳到后一题,这样只会浪费更多时间(唉

4. 代码

#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;

int n, num, cnt;
int l = 0, r = 25, letter[26];
string s, ans;
bool flag;

void solve()
{
    memset(letter, 0, sizeof(letter));
    cnt = 0; num = 26; ans = "";
    queue<int> mark; //记录问号出现的位置
    for (int i = l; i <= r; i++)
    {
        if (s[i] == '?')
        {
            cnt++;
            mark.push(i);
        }
        else
        {
            if (!letter[s[i] - 'A'])
                num--;
            letter[s[i] - 'A']++;
        }
        ans += s[i];
    }
    
    if (num > cnt)
    {
        flag = false;
        return;
    }
    else
    {
        for (int i = 0; i < 26; i++)
        {
            if (!letter[i]) //把问号所在的位置换成letter[i]
            {
                int pos = mark.front();
                mark.pop();
                ans[pos - l] = i + 'A';
            }
        }
        cout << ans << endl;
        flag = true;
        return;
    }
    
}

int main()
{
    cin >> s;
    long n = s.length() - 1;
 
    while (r <= n)
    {
        solve();
        if (!flag)
        {
            l++;
            r++;
        }
        else
            break;
    }
    
    if (!flag)
        cout << "-1" << endl;
    
    
    return 0;
}

C 咕咕东的奇妙序列

1. 题目大意

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

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

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

样例:

5
1
3
20
38
56
1
2
5
2
0

2. 思路历程

  • (这道题的细节也太绕了叭)考虑序列中每段的长度(1 ~ n)求和得到整个序列各个段的长度和(1) + (1 ~ 2) + … + (1 ~ n),即ssum(x) = ∑sum(x)

  • 由于 [10i, 10i+1) 的数长度均为 i + 1,所以按照每段最大数的位数划分部分,(如最大数为1 ~ 9属于1位数部分,最大数为10 ~ 99属于2位数部分)从 (1 ~ 10i) … (1 ~ 10i+1 - 1) 的i位数部分内所有sum(i)构成一个等差数列,差为i + 1

  • 对于给定的x,先求出其所在于哪个位数的部分,再求出其位于该部分中的哪一个数(这里用两次二分法来确定)

  1. i 位数部分和 = (10i - 1) * 1位数部分和 + (10i - 9)* 2位数部分和 + … + (10i - 10i-1 + 1) * i 位数部分和
  2. i 位数部分的段的和 = 1 * 9 + 2 * 90 + … + i * (10i - 10i-1)
    <–有点像分割求直角三角形的面积–>
  3. 对于x找到最后一个比其小的部分和,再找到其所在的段,最后找到其在段内的哪个数字的哪一位

3. 反思

本来以为可以骗点分,可能要怪自己骗分也太有逻辑(?
我还把序列中的每个部分按照位数进行了讨论,为啥不直接把一部分序列存在代码里面嘞(?
感觉骗分也是个比较重要的技能,既然已经不会了就贯彻到底

4. 代码

#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;

long long q, k;

long long get_len(long long x)
{
    return x * (x + 1) / 2;
}


long long get_sum(long long x)
{
    long long ans = 0, part = 1, d = 1;
    for (; x >= part * 10; part *= 10, d++)
    {
        ans += d * get_len(part * 9) + d * part * 9 * (x - part * 10 + 1);
    }
    return ans + d * get_len(x - part + 1);
}

long long get_num(long long x)
{
    long long ans = 0, part = 1, d = 1;
    for (; x >= part * 10; part *= 10, d++)
    {
        ans += d * part * 9;
    }
    return ans + d * (x - part + 1);
}
 

int main()
{
    cin >> q;
    for (int i = 0; i < q; i++)
    {
        cin >> k;
        long long l = 0, r = 1e9, ans1 = 0, ans2 = 0;
        
        while (l <= r)
        {
            long long mid = (l + r) >> 1;
            if (get_sum(mid) < k) // 找到k位于哪一块
            {
                l = mid + 1;
                ans1 = mid;
            }
            else
                r = mid - 1;
        }
        
        k -= get_sum(ans1);
        l = 0; r = ans1 + 1;
        while (l <= r)
        {
            long long mid = (l + r) >> 1;
            if (get_num(mid) < k)
            {
                l = mid + 1;
                ans2 = mid;
            }
            else
                r = mid - 1;
        }
        k -= get_num(ans2++);
        
        int a[30], j = 0;
        memset(a, 0, sizeof(a));
        while (ans2)
        {
            a[j++] = ans2 % 10;
            ans2 /= 10;
        }
        cout << a[j - k] << endl;
        
    }
    
    return 0;
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值