【最小得分和】

[题目描述]

给定一个长度为 N 的正整数序列{ܽܰ},设数对(i,j)的得分 Sij=|ai-aj|,你需要找出 K 对数,使得这 K 对数的得分之和最小。

[输入格式]

第一行有两个正整数 N,K,如题所述。接下来一行有 N 个正整数,表示序列中的数。 [输出格式] 只有一个数,表示最小的得分之和。

[样例输入]

5 5 5 3 1 4 2

[样例输出]

6

[样例解释]

以下括号里的数表示下标。

选择(1,4),(2,4),(2,5),(3,5)这 4 对数,得分之和为 4。再从(1,2),(2,3),(4,5)中任选 1 对,因为这 3 对的得分都一样,都为 2。选择的数对(i,j)中 i,j 不能相等,且(i,j),(j,i)表示同一个数对,也就是说只能选其中一个。

[数据范围]

image

对于 100%的数据:ai≤10 8,保证答案不超过 int64 范围。

题解:

      ①根据数据范围可以得出,最后的二元组(i,j)早已超过可以一一枚举统计答案的范围。

      ②当发现++式统计答案会T的时候,考虑数学公式、二分等。

      ③二分最大差值,判断时用单调队列维护最长小于等于二分差值的窗口,统计二元组个数。

      ④一个细节:假如二分到x超过了k个,x-1的时候不超过k个,那么意思说k没有满,那么直接剩余个数乘上x就可以了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll a[1000006];
ll ans,k;
ll n;
void read(ll &x){
	static ll f; static char ch;
	x=0; f=1; ch=getchar();
	while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
	while('0'<=ch&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	x=x*f;
}
ll check(ll lim){
	ll j=1; ll cnt=0,sum=0; ans=0;
	for(ll i=1;i<=n;i++){
		while(j<i&&lim<a[i]-a[j]) sum-=a[j],j++;
		cnt+=i-j; ans+=a[i]*(i-j)-sum;
		sum+=a[i];
	}
	return cnt;
}
void binary(ll l,ll r){
	ll mid,maxder=0;
	while(l<=r){
		mid=(l+r)>>1;
		if(check(mid)>=k) maxder=mid,r=mid-1;
		else l=mid+1;
	}
	ll kk=check(maxder-1);
	ans+=maxder*(k-kk);
	cout<<ans;
}
int main(){
	freopen("mark.in","r",stdin);
	freopen("mark.out","w",stdout);
	read(n); read(k);
	for(ll i=1;i<=n;i++) read(a[i]);
	sort(a+1,a+n+1);
	binary(0,a[n]-a[1]);
	return 0;
}//*ZJ
原文地址:https://www.cnblogs.com/Damitu/p/7704835.html