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

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章