P1678 烦恼的高考志愿 二分,混搭排序
题目背景
计算机竞赛小组的神牛V神终于结束了高考,然而作为班长的他还不能闲下来,班主任老t给了他一个艰巨的任务:帮同学找出最合理的大学填报方案。可是v神太忙了,身后还有一群小姑娘等着和他约会,于是他想到了同为计算机竞赛小组的你,请你帮他完成这个艰巨的任务。
题目描述
现有 m(m≤100000)m(m\le100000)m(m≤100000) 所学校,每所学校预计分数线是 ai(ai≤106)a_i(a_i\le10^6)ai(ai≤106)。有 n(n≤100000)n(n\le100000)n(n≤100000) 位学生,估分分别为 bi(bi≤106)b_i(b_i\le10^6)bi(bi≤106)。
根据n位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。
输入格式
第一行读入两个整数m,n。m表示学校数,n表示学生数。第二行共有m个数,表示m个学校的预计录取分数。第三行有n个数,表示n个学生的估分成绩。
输出格式
一行,为最小的不满度之和。
输入输出样例
4 3 513 598 567 689 500 600 550
32
说明/提示
数据范围:
对于30%的数据,m,n<=1000,估分和录取线<=10000;
对于100%的数据,n,m<=100,000,录取线<=1000000。
首先转换题意
给定n个数,m次询问,每次询问会给出一个数a,要求从n个数中找到一个最接近a的数字b,求出a和b的差值的绝对值并累加,最后求m次差值之和的最小值
问题就很显然了,如何快速的在一组数中找到与a最接近的数字呢
由于一组数已经给出,且有多次询问,很容易想到先对一组数进行排序,然后二分查找
由此引出第一个思路:排序+lower_bound
由于lower_bound返回的是第一个大于等于a的数字,我们并不知道这个数字前一个数字与a的差值会不会更小,因此要在这里两个数中取差值最小,然后对于a比所有数都大和a比所有数都小的情况特判一下,大功告成
然后是第二个很巧妙的思路:混合排序
直接将m次询问的数标记后和给定的n个数存在一个数组里,然后一起排序,接着扫一遍,找到被标记的询问数,就看它前后的数字和它的差值取最小,最后对于第一个和最后一个数特判一下即可
然而上面的解法是有bug的,如果询问了多次相同的数,就会在排序后出现aaa的情况,中间的a与前后数的差值都是0,这个结果显然是不对的
有两种解决的办法,第一种是从这个数同时向前向后找,用找到的第一个非标记数来计算差值;第二个是在输入时便用桶排将相同的询问数直接合并,计算时把差值乘以对应询问数出现的次数即可
贴上第一种思路的代码
#include<bits/stdc++.h> using namespace std; long long n,m,a[1000005],b[1000005],ans; int main() { cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=m;i++) cin>>b[i]; sort(a+1,a+n+1); // for(int i=1;i<=n;i++) // cout<<a[i]<<" "; // cout<<endl; for(int i=1;i<=m;i++)//其实这一步完全可以边读入边做,然而最后并没有TLE,就懒得改了 { int k=lower_bound(a+1,a+n+1,b[i])-a; if(k==n+1) { ans+=b[i]-a[n]; continue; } if(k==1) { ans+=a[1]-b[i]; continue; } ans+=min(abs(a[lower_bound(a+1,a+n+1,b[i])-a-1]-b[i]),abs(a[lower_bound(a+1,a+n+1,b[i])-a]-b[i])); } cout<<ans; return 0; }