首先有一个显然的想法:从小到大枚举答案,每次尝试将每个数加进去,如果加进去大小能在枚举的这个答案以内那就加进去,否则就不加。容易发现这是\(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);
}
标签:int,761,mid,IOI2022,using,UOJ,Fl,Id,define
From: https://www.cnblogs.com/275307894a/p/16712114.html