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  
#include  
#include  
#include  
#include  
#include  
#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;ii) report(i,p);  
        } 
    } 
}; 

namespace task3 {   
    int sta[N],sum[N],prev[N];  
    queueq;  
    void solve(int n,int m) {      
        int MAX=n-1;                
        for(int i=0;(1<a) {           
        if(l==r) {                   
            for(int i=0;i>1;     
        vectorpl,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;ig; 
        work(0,n-1,g);  
    }
};   

namespace taskF {     
    vectortmp; 
    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&a) {   
        if(l==r) {  
            for(int i=0;iql,qr;  
        int mid=(l+r)>>1;   
        for(int i=l;i<=mid;++i) {      
            modify(arr[i]),mark[arr[i]]=1;  
        }      
        for(int i=0;itmp; 
            for(int i=0;i 
   

  

你可能感兴趣的:(LOJ#3161. 「NOI2019」I 君的探险 整体二分+随机化+二进制分组)