把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

UOJ #761. 【IOI2022】最罕见的昆虫

题面传送门

首先有一个显然的想法:从小到大枚举答案,每次尝试将每个数加进去,如果加进去大小能在枚举的这个答案以内那就加进去,否则就不加。容易发现这是\(O(n^2)\)的询问次数,能过10pts

我们发现这个枚举答案的过程实际上是有单调性的,因此可以二分,每次仍然扫一遍,询问次数\(O(n\log n)\)似乎能过50pts。

这里我们将每次的二分过程独立开来,肯定没有办法挖掘到一些有用的信息。因此我们考虑缩小范围。

首先可以先用\(n\)次询问问出有几种数,记为\(C\),显然答案不会超过\(\frac{n}{C}\)

则当前答案所处区间为\([1,\frac{n}{C}]\),取中点\(mid\),看mid能否取到。

\(mid\)可以取到,那么当前\(mid\times C\)个数是没有用的,因为在更高的答案中依然可以取到,因此下一层可以少掉一半的询问次数。

\(mid\)不能取到,则剩下\(n-mid\times C\)个数也是没有用的因为答案更小,所以不会被取到。

可以发现每次询问次数会减半,则询问次数为\(3n\)级别的。然后你会获得99pts的好成绩。

这个看上去其实\(3n\)次询问很对,为什么会错呢?

实际上如果当前二分区间答案为奇数,则会造成两边划分不均的情况,也就是说在最坏情况下会做到\(3n+C\log n\)的复杂度,因此当这种情况的时候我们随机划分,期望就是\(3n\)次询问的,多交几遍就好了。脸黑的作者交了5遍才过/kx

code:

#include "insects.h"
#include<bits/stdc++.h>
#define Gc() getchar() 
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((m)*(x-1)+(y))
#define R(n) (rnd()%(n)+1)
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
using ll=long long;using db=double;using lb=long db;using ui=unsigned;using ull=unsigned ll;
using namespace std;const int N=2e3+5,M=(1<<20)+5,K=1e5+5,mod=998244353,Mod=mod-1;const db eps=1e-5;mt19937 rnd(time(0));
int n,Si,Fl[N];vector<int> Id;
int Solve(int l,int r,vector<int> Id){
	if(l==r) return l;vector<int> P;P.clear();int m=l+r>>1,Ct=0;if(R(2)^1&&(r-l+1)&1&&Si*(r-l)==Id.size()) m--; 
	for(int i:Id) if(Ct!=(m-l+1)*Si) move_inside(i),press_button()>m-l+1?(move_outside(i),0):(Ct++,Fl[i]=1);
	if(Ct==Si*(m-l+1)) {for(int i:Id) !Fl[i]&&(P.PB(i),0),Fl[i]&&(move_outside(i),0);Me(Fl,0);return Solve(m+1,r,P);}
	for(int i:Id) Fl[i]&&(P.PB(i),move_outside(i),0);Me(Fl,0);return Solve(l,m,P);
}
int min_cardinality(int Ns) {
	int i;n=Ns;for(i=0;i<n;i++) move_inside(i),press_button()>1?(move_outside(i),0):(Si++,Fl[i]=1);cerr<<Si<<'\n';
	for(i=0;i<n;i++) !Fl[i]?Id.PB(i):move_outside(i);Me(Fl,0);if(Si==n) return 1;return Solve(1,Ns/Si,Id);
}
posted @ 2022-09-20 18:36  275307894a  阅读(47)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end