考虑暴力做法:从小到大枚举亲密度,算出各个亲密度下有多少对数,然后相加直到为k。时间复杂度为O(n ^ 2)
二分做法:二分亲密度mid,算出亲密度<=mid时有多少对,如果有大于等于k对时答案显然在左边,否则在右边。
然后问题就是怎么算出亲密度<=mid有多少对数。比赛时就死在这里,想到了可能是二分亲密度,但没有想到怎么算有多少对数。这里给出两种方法。
①直接法:因为要求亲密度<=mid的对数,因此,可以枚举长度为mid 的区间。使用双指针算法,左指针为l,右指针为r,每次右指针向右移动一位,算出他左边长度为mid的区间内有多少和它不一样的数,然后再左指针右移。过程中直接用桶记录数据即可。
②考虑容斥:长度为mid的区间的满足要求的对数 = 所有对数-不满足条件的对数(即相同的数)(1)可以按照如上方法进行双指针的算法。
(2)也可以开一个vector数组,将所有相同的数放在一个桶,然后分别对每个桶枚举即可。
#include <iostream>
#include <cstring>
const int N = 1e5 + 10;
using namespace std;
typedef long long LL;
LL n; LL k;
int a[N], st[N];
LL check(LL mid)
{
memset(st, 0, sizeof st);
LL sum = 0;
for (int i = 1; i <= mid + 1 && i <= n; i ++)
{
sum += ((i - 1) - st[a[i]]);
st[a[i]] ++;
}
LL l = 1, r = mid + 2;
while(r <= n)
{
st[a[l]] --;
l ++;
sum += (mid - st[a[r]]);
st[a[r]] ++;
r ++;
}
// cout << "---" << mid << "--" << sum << endl;
return sum;
}
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i ++) cin >> a[i];
LL l = 1, r = n + 1;
while(l < r)
{
LL mid = l + r >> 1;
if (check(mid) >= k) r = mid;
else l = mid + 1;
}
LL cnt = check(l - 1);
cnt = k - cnt;
LL num = 0;
if (l == n + 1)
cout << -1;
else
{
for(int i = 1; ;i ++)
{
if (a[i] != a[i + l])
num ++;
if (num == cnt)
{
cout << i << ' ' << i + l;
break;
}
}
}
}