LOJ3834 「IOI2022」最罕见的昆虫
题目描述
这是一道交互题。
交互库有一个长为 \(n\) 的数组 \(a\) 。
你需要实现以下函数:
-
int min_cardinality(int N):返回数组 \(a\) 中出现次数最少的数的出现次数。
你可以调用以下函数:
-
void move_inside(int i):将 \(a_i\) 加入可重集 \(S\) 。
-
void move_outside(int i):将 \(a_i\) 从 \(S\) 中删除,如果 \(a_i\) 不在 \(S\) 中则不执行任何操作。
-
int press_button():询问 \(S\) 中出现次数最多的数出现次数。
每个函数最多可以调用 \(3n\) 次。
数据范围
- \(1\le n\le 2000\) 。
时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{2048MB}\) 。
分析
保持集合中每个数最多出现 \(1\) 次,可以用 \(n\) 次操作得到不同颜色种类数,记为 \(k\) 。
二分答案,保持集合中每个数最多出现 \(x\) 次,那么判断答案 \(\ge x\) 等价于判断集合大小是否等于 \(kx\) 。
于是我们得到了一个操作次数 \(\mathcal O(n\log n)\) 的做法。
考虑优化。
- 如果答案 \(<x\) ,那么每种颜色保留 \(x\) 个已经足够,无需需要考虑集合外的数。
- 如果答案 \(\ge x\) ,那么后面二分时不再操作集合内的 \(kx\) 个数。
再来分析一下操作次数。
假设我们已经二分到区间 \([l,r]\) ,那么只需要考虑不超过 \((r-l+1)\cdot k\) 个数。
由于每次二分值域减半,因此操作次数 \(\approx n+(n+\frac n2+\frac n4+\cdots)\approx 3n\) 。
直接交可以获得 \(99pts\) 的成绩,原因是每次二分时,如果 \(l,r\) 奇偶性不同,我们无法恰好取在区间中点。
加上一点随机扰动,每次取 mid=(l+r+(rnd()&1))/2 即可,时间复杂度\(O(n\log n)\)。
有可能需要一个好一点的随机种子。
#include<bits/stdc++.h>
#include"insects.h"
#define add move_inside
#define del move_outside
#define query press_button
using namespace std;
const int maxn=2005;
int k,n;
bool has[maxn],vis[maxn];
mt19937 rnd(998244353);
bool check(int x)
{
for(int i=0;i<n;i++)
{
if(vis[i]) continue;
add(i);
if(query()<=x) has[i]=true;
else del(i);
}
int cnt=0;
for(int i=0;i<n;i++) cnt+=has[i];
if(cnt==k*x)
{
for(int i=0;i<n;i++) vis[i]|=has[i];
return true;
}
else
{
for(int i=0;i<n;i++)
if(!vis[i]&&has[i]) has[i]=false,del(i);
else vis[i]=true;
return false;
}
}
int min_cardinality(int _n)
{
n=_n;
for(int i=0;i<n;i++)
{
add(i);
if(query()==1) has[i]=vis[i]=true,k++;
else del(i);
}
int l=1,r=n/k+1;
while(r-l>1)
{
int mid=(l+r+(rnd()&1))/2;
if(check(mid)) l=mid;
else r=mid;
}
return l;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/17063275.html
浙公网安备 33010602011771号