2023第五届河南省CCPC大学生程序设计竞赛题解

A题:小水獭游河南

题意:给定一个字符串,判断是否由一个只含只由单个字母组成的字符串和一个回文串前后拼接而成

Solution

将只由单个字母组成的字符串称为S_{1},回文串称为S_{2},由于最多只有26个单个英文字母,所以

\left | S_{1} \right | \leqslant 26 。对给定的字符串从前往后开始枚举,每次枚举判断后面的字符串是否构成回文串,枚举的次数一定小于27,时间复杂度为线性级别

Code

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
#define int long long

string s;
int sv(int x)
{
    for (int i = x + 1; i <= (s.size() + x) / 2; i++)
        if (s[i] != s[s.size() + x - i])return 0;
    return 1;
}

signed main()
{
    ios::sync_with_stdio(0); cout.tie(0); cin.tie(0);
    int t; cin >> t;
    while (t--)
    {
        unordered_map<char, int>a;
        cin >> s;
        if(s.size()==1)注意特判size()=1
        {
            cout<<"NaN"<<endl;
            continue;
        }
        for (int i = 0; i < s.size(); i++)
        {
            if (a[s[i]] == 0)
            {
                if (sv(i))
                {
                    cout << "HE" << endl;
                    break;
                }
                a[s[i]]++;
            }
            else//如果出现了a[s[i]]==1,说明前面的所有串都不能构成正确的S1,则一定不存在
            {
                cout << "NaN" << endl;
                break;
            }
        }
    }
    return 0;
}

H题:Travel Begins

题意:给出两个正整数N,K,将数字N分成K个非负实数,K个实数若存在小数且小数部分>=0.5,则对答案贡献向上取整,否则向下取整,问K个实数贡献求和所能取得的最小和最大值

Solution

1.MAX

初始时K个数字为0,考虑这样一个事实:若分给一个数字0.5,则其会变成1,而后只能给他1及以上的值,其才能变为2,对答案贡献(2-1(1是其原来的贡献)=1)而若将给他的1变为2个0.5,交给2个为0的值,对答案贡献为2-0=2,所以我们先给是0的地方0.5,使其贡献为1,若没有剩余,则所有数加起来之和就是最大贡献,若有剩余,对于任意一个数字,给他1的倍数是最划算的。

2.MIN

初始时K个数字为0,考虑这样一个事实:若分给一个数字0.499999999......,对答案贡献仍是0,

我们先对每个数字分0.4999999....,若分到最后没有剩余,则答案为0,若有剩余,则将其分给任意一个数字,且它的小数部分加上0.4999999...一定大于0.5,所以对剩余的值向上取整即可

注意:不能使用ceil和floor,会有精度丢失(wa了5发得来的教训。。。)

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long

signed main()
{
    ios::sync_with_stdio(0); cout.tie(0); cin.tie(0);
    int t; cin >> t;
    while (t--)
    {
        int n, k; cin >> n >> k;
        if (n * 2 < k)cout << 0 << " ";
        else
        {
            cout << (int)(n - 0.4999999999999 * k+1) << " ";
        }
        if (n * 2 <= k)
            cout << n * 2 << endl;
        else cout << k + (int)(n - 0.5 * k) << endl;
    }
    return 0;
}

F题:Art for Last

题意:给出一个长为n的非负整数序列和数字k,要求从序列中选出k(K\geqslant 2)个数字使得 任意两项作差的绝对值的最小值*任意两项作差的绝对值的最大值  最小

Solution

1.作差的绝对值的最小值

若想使得该值最小,可以想到一定是长度为k的序列中相差最近的两个值的差

2.作差的绝对值的最大值

若想使得该值最小,也可以想到,当长度为k的序列中最大的值与序列中最小的值相差最近时。,该值最小

所以对序列进行排序,从前往后开始枚举,最大值可直接求得,但最小值每次枚举需要遍历一次长度为k的序列,必然超时,考虑维持原序列的差分数组CF,若要求序列\left [ l,l+k-1 \right ] 的作差最小值,则是求差分数组中\left [l+1,l+k-1 \right ]的最小值,这里可用滑动窗口求最小值,也可以用线段树维护区间最小值,下面给出代码为线段树

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5 + 10;

int a[N];
int cf[N];

struct node {
    int l, r, num, min;
}tr[N * 4];

void pushup(int u)
{
    tr[u].min = min(tr[u << 1].min, tr[u << 1 | 1].min);
}

void build(int u, int l, int r)
{
    if (l == r)
    {
        tr[u] = { l,l,cf[l],cf[l] };
        return;
    }
    else
    {
        tr[u] = { l,r };
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
        return;
    }
}


int query(int u, int l, int r)
{
    if (l <= tr[u].l && r >= tr[u].r)return tr[u].min;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        int min1 = 1e9;
        if (l <= mid)min1 = query(u << 1, l, r);
        if (r > mid)min1 = min(min1, query(u << 1 | 1, l, r));
        return min1;
    }
}

signed main()
{
    ios::sync_with_stdio(0); cout.tie(0); cin.tie(0);
    int n, k; cin >> n >> k;
    for (int i = 1; i <= n; i++)cin >> a[i]; 
    sort(a + 1, a + 1 + n);
    for (int i = 1; i <= n; i++)cf[i] = a[i] - a[i - 1];
    build(1, 2, n);
    int min1 = 1e18;
    for (int i = k; i <= n; i++)
    {
        min1 = min(min1, (a[i]-a[i-k+1]) * query(1, i - k + 2, i));
    }
    cout << min1;
    return 0;
}

B题:Art for Rest

题意:给出一个长度为n的序列,每次将序列按顺序分成多端,每段长度为K1\leqslant K\leqslant n,每次对每段从小到大进行排序,问存在多少个使得序列单调不减

Solution

首先想到线段树维护区间最值,只要每段的最大值小于等于下一段的最小值即可,但n过大,k从1到n,每次查询n/k,达到n方级别

正确解法:维护前缀最大值和后缀最小值,每次我们只需要考虑前缀最大值是否大于后缀最小值即可

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;

int pre[N], back[N];
int a[N];
signed main()
{
    ios::sync_with_stdio(0); cout.tie(0); cin.tie(0);
    int n; cin >> n;
    for (int i = 1; i <= n; i++)cin >> a[i];
    pre[1] = a[1], back[n] = a[n];
    for (int i = 2; i <= n; i++)pre[i] = max(pre[i - 1], a[i]);
    for (int i = n - 1; i >= 1; i--)back[i] = min(back[i + 1], a[i]);
    int cnt = 0;
    for (int k = 1; k <= n; k++)
    {
        int p = 0;
        for (int i = 1; i <= n; i += k)
        {
            int j = i + k - 1;
            if(j+1>n)break;
            if (pre[j] > back[j+1])
            {
                p = 1;
                break;
            }
        }
        if (!p)++cnt;
    }
    cout << cnt;
    return 0;
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值