比較神仙的隨機化+交互題.
測試點 $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); } }