分组
题目
蓝桥小学要进行弹弹球游戏,二年级一班总共有 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;
}