51nod 1686 第K大区间 二分瞎搞

题目:

定义一个区间的值为其众数出现的次数。
现给出n个数,求将所有区间的值排序后,第K大的值为多少。

题解:

答案明显单调,我们考虑二分答案.
转化为判定问题后我们需要观察到一个性质:

如果一个区间的价值已经 >= mid了,那么这个无论左右端点向外延伸多少,区间价值一定仍然 >= mid

所以我们可以考虑找出所有的最小满足限制的区间,然后计算出可延伸区间。
所以我们枚举右端点,根据右端点找出对应的左端点,然后统计答案.
我们发现:根据右端点向右移动,左端点一定也向右移动。
所以我们使用双指针法,每一次计算出右端点所代表的值的上mid-1次出现的位置
用这个位置和左端点指针取max,每一次让ans += 所有<=下标左端点的点的个数.

单次判定(O(n)),总体(O(nlogn))

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
	x=0;char ch;bool flag = false;
	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
inline void read(ll &x){
	x=0;char ch;bool flag = false;
	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
const int maxn = 120000;
int last[maxn],nxt[maxn];
int b[maxn],n,a[maxn],c[maxn],pos[maxn];
inline void init(){
	memset(last,0,sizeof last);
	memset(nxt,0,sizeof nxt);
	memset(b,0,sizeof b);
}
inline ll check(int val){
	ll ret = 0;
	init();memset(c,0,sizeof c);
	for(int i=1;i<=n;++i){
		last[i] = c[a[i]];
		c[a[i]] = i;
	}memset(c,0,sizeof c);
	for(int i=n;i>=1;--i){
		nxt[i] = c[a[i]];
		c[a[i]] = i;
	}
	memset(c,0,sizeof c);
	memset(pos,0,sizeof pos);
	for(int i=1;i<=n;++i){
		if(++c[a[i]] == 1) pos[a[i]] = i;
		if(c[a[i]] == val) b[i] = pos[a[i]];
		else if(c[a[i]] > val){
			b[i] = nxt[b[last[i]]];
		}
	}
	if(val == 1) for(int i=1;i<=n;++i) b[i] = i;
	for(int L=0,R=1;R <= n;++R){
		L = max(L,b[R]);
		ret += L;
	}
	return ret;
}
int main(){
	read(n);ll k;read(k);
	for(int i=1;i<=n;++i){
		read(a[i]);b[i] = a[i];
	}
	sort(b+1,b+n+1);
	for(int i=1;i<=n;++i){
		a[i] = lower_bound(b+1,b+n+1,a[i]) - b;
	}
	int l = 1,r = n,ans = 0;
	while(l <= r){
		int mid = (l+r) >> 1;
		if(check(mid) >= k) ans = mid,l = mid+1;
		else r = mid-1;
	}
	printf("%d
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/Skyminer/p/6601981.html