给定n个数A1...An,小Ho想了解AL..AR中有多少对元素值相同。小Ho把这个数目定义为区间[L,R]的价值,用v[L,R]表示。
例如1 1 1 2 2这五个数所组成的区间的价值为4。
现在小Ho想知道在所有的的v[L,R](1 <= L <= R <= n)中,第k小的值是多少。
Input第一行一个数T(T<=10),表示数据组数。
对于每一组数据:
第一行两个数n,k(1<=n<=200,000,1<=k<=n*(n+1)/2)
第二行n个数A1…An(1<=Ai<=1,000,000,000)
Output一个数表示答案。
Sample Input
2 4 7 1 1 2 3 3 6 100 100 100
Sample Output
0 3
题意:我们给出n个数,我们求任意一段区间,他们相同的数的次数就是区间的值,然后我们按值排序求第k个区间的值是多少
思路:开始我用的n2,果断超时。。然后我们看到ai的范围有这么大,我们又要记录次数,显然我们可以用map,但是我用map也超时了,
所以有个高级的操作,因为n的范围数组能开的下,只是ai值大而已,所以我们可以离散化,然后我们想一下,怎么求答案呢,如果我们直接求出所有的区间再排序输出的话n2复杂度
所以发现不行,我们仔细想想,我们能得知我们区间长度越小,我们的区间值肯定更小,我们可以二分去处理,二分的话最小值是0没有一个相同,最大的时候也就是全部的数都相同,可以推出是n*(n-1)/2
因为我们要求是求第k个区间的值,那么我们就只要去寻找判断,小于当前数的区间个数有多少个,如果小于这个数的区间比k还大的话,说明我们当前的数肯定比我们要求的小,所以我们向右扩展,反之亦然
然后我们想如何去求多少个区间比他小呢?
我们可以不用求出所有区间的值为什么呢,因为我们区间的个数和值的大小息息相关
如果[l.r]是比k小的,那么[l,r-1],[l,r-2]....[l,l]都是小于k的数,这里就用到了我们的尺取法
那么我们就把它变成了一个nlogn的算法
#include<cstdio> #include<cmath> #include<cstring> #include<map> #include<algorithm> using namespace std; typedef long long ll; ll a[200001]; ll t,n,m,temp[200001]; ll vis[200001]; ll check(ll mid)//尺取求比mid小的区间个数 { int i,j; ll sum=0; ll num=0; memset(vis,0,sizeof(vis)); for(i=0,j=0;i<n;i++) { for(;j<n&&sum+vis[a[j]]<=mid;j++) { sum+=vis[a[j]]; vis[a[j]]++; } num+=j-i;//尺取思想核心 vis[a[i]]--; sum-=vis[a[i]]; } return num>=m; } int main() { ll ans; scanf("%lld",&t); while(t--) { scanf("%lld%lld",&n,&m); for(int i=0;i<n;i++) { scanf("%lld",&a[i]); temp[i]=a[i]; } int cnt; sort(temp,temp+n);//离散化 cnt = unique(temp,temp+n) - temp; for(int i = 0 ; i < n ; ++i)a[i] = lower_bound(temp,temp+cnt,a[i]) - temp; ll left=0,right=((ll)n*((ll)n-1))/2; while(left<=right) { ll mid=(left+right)/2; if(check(mid))//如果小于mid的区间个数比m多的话,说明值还不够小 { ans=mid; right=mid-1; } else{ left=mid+1; } } printf("%lld\n",ans); } }