蓝桥杯刷题记day2

文章讲述了如何解决分组问题,最小化身高极差,通过二分搜索和排序策略,以及如何计算字符串子串贡献值,利用哈希数组记录字母位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分组

题目

蓝桥小学要进行弹弹球游戏,二年级一班总共有 n 个同学,要求分成 k 个队伍,由于弹弹球游戏要求队员的身高差不能太大,小蓝是班长,他对这个事情正在发愁,他想问你,如何最小化每个组之间的身高极差。

具体的,假设分成了 k 个组,第 i 组最高的人身高是 Hxi​ ,最矮的是 Hni​,直白来说,你需要将 n 个元素分出 k 组,使得最大的极差尽可能小。你需要输出这个最小化后的值。

输入

第一行输入两个整数 �,�n,k 。

第二行输入 �n 个整数:ℎ1,ℎ2,ℎ3...ℎ�h1​,h2​,h3​...hn​ ,分别代表 �n 个人的身高。

输出

输出一个整数,代表最小值。

样例输入

5 3
8 4 3 6 9

样例输出 

1

说明 

样例分组情况:{ 3,4,4 } ,{ 6 } ,{ 8,9,9 } 。

解题思路

题意:假设有分为k组有n种方案,要求出每种方案中k组中最大极差为所有方案的最小值,即最大值最小化。最大值最小化,想到用二分答案,即二分最大极差。 首先我们很容易想到排序,在样例中也有提示。在排完序后,相邻两数的差值最小,两数距离越远,极差越大。这样可以让我们在最大极差确定的情况下,选取每组两端求极差看是否合法即可。

那代码具体如何实现?

排序后,二分最大极差a[n]-a[i]得到mid,mid即我们确定的最大极差,在二分过程中不断最小化。确定最大极差后,开始分组:第一组肯定以a[1]为最小值(由于每组最小值在变,故用start记录),然后枚举a[i],不断计算a[i]-start是否大于mid,一旦大于mid,a[i]就不能继续放在这一组了。另开一组,以stat=a[i]为最小值,继续枚举后面的a[i]直至该组极差大于mid,依次类推。

注意题目中还有一个条件,要求分为k组!

上述过程中假设得到cnt组,那么可能cnt<k或cnt=k或cnt>k。cnt=k就不用讨论了肯定行,接下来讨论cnt<k和cnt>k:发现当cnt<k时,如:样例:{3,4},{6},{8,9},现在改为k=4组,我们就可以从{8,9}中拿一组来,有{8},{9},这样不会影响mid,同时也满足k,故cnt<k可行。而cnt>k就不行了,因为多余的组不能压缩了。

code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+9;
int a[N];
int n,k,mid;
bool check(int x){
    int start=a[1];
    int cnt=1;
    for(int i=2;i<=n;i++){
        if(a[i]-start>mid){//极差小于mid的分为一组,凡是大于的,就要分为另一组,保证最大极差为mid
            cnt++;//统计最大极差为mid时,能分为几组
            start=a[i];//更新新的一组的最小值
        }
    }return cnt<=k;//当cnt<=k时,可以从合法组中拿出元素来凑满k组
}
signed main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>a[i];
    sort(a+1,a+n+1);
    int  l=-1,r=a[n]-a[1];//以右边为答案l-1;
    while(l+1!=r){
        mid=r+l+1>>1;
        if(check(mid)){
            r=mid;
        }else{
            l=mid;
        }
    }cout<<r;
    return 0;
}

子串分值

题目

对于一个字符串 S,我们定义 S 的分值 f(S) 为 S 中恰好出现一次的字符个数。例如 f(aba)=1,f(abc)=3,f(aaa)=0。

现在给定一个字符串 S0⋯Sn−1​(长度为 n,1≤1051≤n≤105),请你计算对于所有 S 的非空子串 Si⋯j​(0≤i≤j<n)f(Si⋯j​) 的和是多少。

输入描述

输入一行包含一个由小写字母组成的字符串 S。

输出描述

输出一个整数表示答案。

输入输出样例

示例

输入

ababc

输出 

21

思路 

参考题解大佬的思路:本题关键理解字母的贡献值。 比如样例中:ababc aba中a的贡献值为0,要想a有贡献值,选取的子串就必须是只含有一个a 比如第二个a,从左边到它有ba,a子串,从右边到它有abc,ab,a.共2*3=6种; 第一个a,从左边到它子串a,从右边到它有ab,a,共1*2=2种; 第一个b,从左到它有子串ab,b,从右到它有子串ba,b,共2*2=4种 第二个b,从左到它有子串ab,b,从右到它有子串bc,c,共2*2=4种 对于c只有一个,也可以左右分析,以统一做法:从左到它有子串ababc,babc,abc,bc,c,从右它有子串c,共5*1=5种 所以加起来有21种 从中我们可以发现规律:枚举每一位字母,从左到它的子串数*从右到它的子串数=该字母贡献值为1时所有子串数 再将该规律公式化: 从左到枚举的字母的子串数=枚举字母的位置-(与枚举字母相同且最近的位置 || 0)0为左边没有与枚举字母相同字母时 从右到枚举字母的子串数=(字符串长度+1 || 与枚举字母相同且最近的位置)-枚举字母的位置 字符串长度+1为右边没有枚举字母相同时

那么如何实现上述规律? 重点在于如何记录:与枚举位置相同且最近的位置。 以左边为例子,大体的思路就是开一个哈希数组初始化为0,从左到右遍历,将枚举的字母数字化 成哈希数组的下标,哈希数组记录该种字母的位置,我们可以发现,对于ababc中的a字母:a数字 化后对应下标上哈希数组的值为依次为0,1,3 所以我们可以专门开一个数组l[N]在哈希数组更新前记录枚举到i位置时,记录i的左边与其相同的位置,如此实现“最近位置”。从右边看同理,注意遍历顺序。

code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+9;
char s[N];
int  t[30];//将字母数字化,作为t数组的下标,改下表对应的值为该字母的位置
int  l[N];//枚举字母位置i,l[i]记录i左边重复出现且离i最近的字母位置,若没有,l[i]=0;
int  r[N];//记录l[i]右边重复出现且离i最近的字母位置,若没有,l[i]=字符串长度+1
signed main()
{
    string a;cin>>a;
    int n=a.size();
    for(int i=1;i<=n;i++)s[i]=a[i-1];
    t[28]={0};
    for(int i=1;i<=n;i++){//枚举字母
        int k=s[i]-'a';//字母数字化并作为t数组下标
        l[i]=t[k];//开始时,i=1,左边没有重复字母,初始化为0;
                  //之后若i前出现过与当前位置重复的字母,更新为重复字母的位置
        t[k]=i;
    }
    for(int i=0;i<30;i++)t[i]=n+1;
    for(int i=n;i>=1;i--){
        int k=s[i]-'a';
        r[i]=t[k];
        t[k]=i;
    }int ans=0;
    for(int i=1;i<=n;i++)ans+=(i-l[i])*(r[i]-i);
    cout<<ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值