LOJ#3161. 「NOI2019」I 君的探险 整体二分+随机化+二进制分组
比较神仙的随机化+交互题.
测试点 $1$ ~ $5$ :
限制条件不强,可以直接点亮一条边中编号小的点 $x$,然后再枚举编号大于 $x$ 的点.
操作次数:$O(n)$
查询次数:$O(n^2)$
测试点 $6$ ~ $9$:
图的形态是点两两匹配.
这里有两种做法:
1. 随机化
假设当前要分点集 $A$ 中的点,那么可以先随机出 $B$ 个点.
对于剩下的 $|A|-B$ 个点,如果状态改变,说明该点也属于 $B$ 集合.
否则,该点不属于 $B$ 集合.
这个复杂度不是严格 $O(n \log n)$ 的
不过可以随机化初始排列,然后$B$ 取 $\frac{1}{3}A$,询问和查询次数都很小.
2. 二进制分组
考虑枚举二进制位 $i$,并将含有 $2^i$ 的点点亮.
那么对于一个点对 $(x,y)$,如果一个被点亮,另一个不被点亮则异或和为 $1$,都被点亮或都不被点亮则为 $0$.
那么我们就可以通过这种方式求出每个点与其相邻点的异或和.
所有操作次数都是严格 $O(n \log n)$.
测试点 $10$ ~ $11$:
启发正解的测试点,不过即使会了这个测试点还是很难想到正解.
由于一个点的父亲节点编号一定小于该节点编号,所以求解父节点编号可以二分.
即点亮一个前缀,然后看 $x$ 的状态是否改变,改变就再向前找,否则向后找.
对于所有点都要这么求,那就上整体二分.
测试点 $12$ ~ $14$:
一条链.
可以采用上面提到的二进制分组做法,求得每个点与其相邻点的异或和.
然后找到 $0$ 号点相邻的两个点,用这两个点向两边扩展.
由于 $0$ 号点将序列从中间断开,所以后面每一个点只受一个点约束,用一个队列来维护就好了.
正解:
正解是整体二分.
考虑有一个集合 $S$ 和点 $x$ (这里 $S$ 中点的编号都小于 $x$)
显然可以通过 $O(|S|)$ 的操作来求得 $S$ 与 $x$ 连边数量的奇偶性.
假如说数量为偶,我们不能确定有没有边,但是为奇的话一定有边.
当确定数量为奇数的时候可以采用二分的方式,即为奇则向前找,为偶则向后找.
想要找到所有点的一条边的时候就采用整体二分的方式.
但是不能直接对于 $1$ ~ $n$ 的排列求解,而是多随机几个排列来做.
每次点和点的大小关系并不是实际大小关系,而是根据排列中的位置来定义的大小关系以保证随机性.
这里有几个需要注意的地方:
- 当一个点周围的所有点都被发现后就应该将这个点除掉.
- 随机数种子直接用系统下得就行,不要用 srand(time(NULL))
代码:(分了 5 个 namespace 写的)
#include <ctime>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 400008
#define pb push_back
using namespace std;
void modify(int x);
int query(int x);
void report(int x, int y);
int check(int x);
namespace task0 {
int sta[N];
void solve(int n,int m) {
for(int i=0;i<n-1;++i) {
modify(i);
for(int j=i+1;j<n;++j) {
int p=query(j);
if(p!=sta[j]) {
report(i,j);
sta[j]=p;
}
}
}
}
};
// 二进制分组
namespace taskA {
int sta[N],sum[N];
void solve(int n,int m) {
int MAX=n-1;
for(int i=0;(1<<i)<=MAX;++i) {
for(int j=0;j<=MAX;++j) {
if(j&(1<<i)) modify(j);
}
for(int j=0;j<=MAX;++j) {
int p=query(j);
if(p!=sta[j]) {
sum[j]^=(1<<i);
sta[j]=p;
}
}
}
for(int i=0;i<n;++i) {
int p=sum[i]^i;
if(p>i) report(i,p);
}
}
};
namespace task3 {
int sta[N],sum[N],prev[N];
queue<int>q;
void solve(int n,int m) {
int MAX=n-1;
for(int i=0;(1<<i)<=MAX;++i) {
for(int j=0;j<=MAX;++j) {
if(j&(1<<i)) modify(j);
}
for(int j=0;j<=MAX;++j) {
int p=query(j);
if(p!=sta[j]) {
sum[j]^=(1<<i);
sta[j]=p;
}
}
}
// 可知每个点的异或和,从 0 开始.
modify(0);
for(int i=1;i<n;++i) {
int p=query(i);
if(p!=sta[i]) {
q.push(i),report(0,i);
}
}
while(!q.empty()) {
int u=q.front(); q.pop();
int aft=sum[u]^prev[u]^u;
if(aft) {
report(u,aft);
prev[aft]=u;
q.push(aft);
}
}
}
};
namespace taskB {
int sta[N];
void work(int l,int r,vector<int>a) {
if(l==r) {
for(int i=0;i<a.size();++i) {
report(a[i],l);
}
return;
}
int mid=(l+r)>>1;
vector<int>pl,pr;
for(int i=l;i<=mid;++i) {
modify(i);
}
for(int i=mid+1;i<=r;++i) {
int cur=query(i);
if(cur) pl.pb(i);
}
for(int i=0;i<a.size();++i) {
int cur=query(a[i]);
if(cur) pl.pb(a[i]);
else pr.pb(a[i]);
}
for(int i=l;i<=mid;++i) {
modify(i);
}
work(l,mid,pl);
work(mid+1,r,pr);
}
void solve(int n,int m) {
vector<int>g;
work(0,n-1,g);
}
};
namespace taskF {
vector<int>tmp;
int mark[N],arr[N];
int hd[N],to[N<<1],nex[N<<1],edges,cnt;
void add(int u,int v) {
nex[++edges]=hd[u];
hd[u]=edges,to[edges]=v;
}
int Query(int x) {
int re=query(x),y;
for(int i=hd[x];i!=-1;i=nex[i]) {
y=to[i];
re^=mark[y];
}
return re;
}
void Report(int x,int y) {
++cnt;
add(y,x),add(x,y),report(x,y);
}
void work(int l,int r,vector<int>&a) {
if(l==r) {
for(int i=0;i<a.size();++i) {
if(a[i]!=l) Report(arr[l],arr[a[i]]);
}
return;
}
vector<int>ql,qr;
int mid=(l+r)>>1;
for(int i=l;i<=mid;++i) {
modify(arr[i]),mark[arr[i]]=1;
}
for(int i=0;i<a.size();++i) {
int v=a[i];
if(v<=mid||Query(arr[v])) {
ql.pb(v);
}
else {
qr.pb(v);
}
}
for(int i=l;i<=mid;++i) {
modify(arr[i]),mark[arr[i]]=0;
}
work(l,mid,ql),work(mid+1,r,qr);
}
void solve(int n,int m) {
memset(hd,-1,sizeof(hd));
for(int i=0;i<n;++i) {
arr[i]=i;
}
random_shuffle(arr,arr+n);
while(cnt<m) {
vector<int>tmp;
for(int i=0;i<n;++i) tmp.pb(i);
work(0,n-1,tmp);
if(cnt<m) {
for(int i=0;i<n;++i) {
if(check(arr[i])) {
swap(arr[i],arr[--n]);
--i;
}
}
random_shuffle(arr,arr+n);
}
}
}
};
void explore(int n,int m) {
if(n<=500) {
task0::solve(n,m);
}
else if(m==n/2) {
taskA::solve(n,m);
}
else if((n%10)==7) {
taskB::solve(n,m);
}
else {
taskF::solve(n,m);
}
}

浙公网安备 33010602011771号