• 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);  
        }  
    }           
    

      

  • 相关阅读:
    Python Django开发遇到的坑(版本不匹配)
    Mysql安装与问题合集
    git branch -r查看不了远程所有分支
    angularJS使用$http请求下载excel表格
    遍历formData对象数据
    按需使用CryptoJS之AES加密(CFB)模式
    git之创建、删除分支
    git pull时报错:Access Denied (拒绝访问)
    angularJS监听数据变化
    Angular-ui-router入门
  • 原文地址:https://www.cnblogs.com/guangheli/p/13446532.html
Copyright © 2020-2023  润新知