AtCoder Beginner Contest 143 F - Distinct Numbers

题意

给出一个长度为NN的序列,求对于所有k[1,N]k\in[1,N],每次从序列中选出kk个互不相同的数,最多能取多少次。
N3e5N\le3e5

题解

我们首先把数组转化为相同的数的出现次数的序列,如序列(1,3,4,4)(1,3,4,4)就转化为(1,1,2)(1,1,2)。把这个得到的序列计作aa

然后二分答案,假设当前二分到xx,能取xx次的条件是:
(aixai)+xai>x1kx\large(\sum_{a_i\le x}a_i)+x\cdot\sum_{a_i>x}1\ge k\cdot x因为同一个数最多取到xx次,而取到的总数为kxk\cdot x

发现当kk增大时答案减小,所以可以把二分去掉,写一个指针动就行了。这样的时间复杂度是O(nlogn)O(n\log n)的,因为要upper_boundupper\_bound

CODE

#include <bits/stdc++.h>
using namespace std;
inline void rd(int &x) {
	char ch; for(;!isdigit(ch=getchar()););
	for(x=ch-'0';isdigit(ch=getchar());)x=x*10+ch-'0';
}
typedef long long LL;
const int MAXN = 300005;
int N, n, a[MAXN], cnt[MAXN];
LL sum[MAXN];
int ans[MAXN];

inline bool chk(int k, int x) {
	int pos = upper_bound(a + 1, a + n + 1, x) - a;
	return sum[pos-1] + 1ll*(n-pos+1)*x >= 1ll*k*x;
}

int main() {
	rd(N);
	for(int i = 1, x; i <= N; ++i) rd(x), ++cnt[x];
	for(int i = 1; i <= 300000; ++i) if(cnt[i]) a[++n] = cnt[i];
	sort(a + 1, a + n + 1);
	for(int i = 1; i <= n; ++i) sum[i] = sum[i-1] + a[i];
	int now = 0;
	for(int k = n; k >= 1; --k) {
		while(now < N && chk(k, now+1)) ++now;
		ans[k] = now;
	}
	for(int i = 1; i <= N; ++i) printf("%d\n", ans[i]);
}

然而看了别人的博客后发现,可以O(n)O(n)
上代码:

sum[x]sum[x]存的就是上面的不等式的左边部分。

#include <bits/stdc++.h>
using namespace std;
inline void rd(int &x) {
	char ch; for(;!isdigit(ch=getchar()););
	for(x=ch-'0';isdigit(ch=getchar());)x=x*10+ch-'0';
}
typedef long long LL;
const int MAXN = 300005;
int n, cnt[MAXN];
LL sum[MAXN];
int ans[MAXN];

inline bool chk(int k, int x) { return sum[x] >= 1ll*k*x; }

int main() {
	rd(n);
	for(int i = 1, x; i <= n; ++i) rd(x), ++cnt[x], ++sum[cnt[x]];
	for(int i = 1; i <= n; ++i) sum[i] += sum[i-1];
	int now = 0;
	for(int k = n; k >= 1; --k) {
		while(now < n && chk(k, now+1)) ++now;
		ans[k] = now;
	}
	for(int i = 1; i <= n; ++i) printf("%d\n", ans[i]);
}

正确性读者自证不难

posted @ 2019-12-14 14:50  _Ark  阅读(125)  评论(0编辑  收藏  举报