CF1659E AND-MEX Walk 題解
題目大意
給定一個無向帶權圖,總共有 \(n\) 個點 \(m\) 條邊,選定一個起點 \(u\) 和終點 \(v\),現在你需要確定一條路徑,設路徑上的 \(k\) 條邊的邊權為 \(w_1,w_2,\dots,w_k\),求出 \(\operatorname{MEX}\left(\{w_1,w_1 \& w_2,w_1\&w_2\&w_3,\dots,w_1\&w_2\&\dots\&w_k\}\right)\) 的最小值。其中 MEX 指集合中最小的不存在的自然數。
資料範圍:\(n,m\le 10^5\),邊權 \(\le 2^{30}\)
題目解析
設路徑上的 \(k\) 條邊的邊權為 \(w_1,w_2,\dots,w_k\),為了方便設序列 \(w_1,w_1 \& w_2,w_1\&w_2\&w_3,\dots,w_1\&w_2\&\dots\&w_k\) 的第 \(i\) 項為 \(a_i\)。
首先我們通過樣例發現一個結論:答案只可能是 \(0,1,2\)。
其實就是證明答案不是 \(0,1\) 的時候答案為 \(2\),證明如下:
我們發現序列 \(\{a_n\}\) 不增。
如果答案不是 \(0,1\),那麼序列中就一定存在 \(0,1\),如果答案不是 \(2\),那麼肯定存在一個數字 \(i\)
這樣我們只需要判斷答案是否為 \(0\),然後判斷答案是否為 \(1\),如果都不是那麼答案就是 \(2\)。
答案為 \(0\) 的時候很簡單,我們只需要判斷是否從 \(u\) 到 \(v\) 存在一條路徑,使得這條路徑上的所有邊權在二進位制下某一位都為 \(1\)。只需要每一位建立一個並查集就可以了。
然後就是答案為 \(1\) 的情況了。
不難發現答案為 \(1\) 的時候,一定存在一個 \(r\) 使得 \(\forall i\le r,a_i>1\) 並且 \(a_{r+1}=0\)
換句話說,只要在走這一條邊之前的與和大於 \(1\),走之後與和為 \(0\) 就可以了,然後接下來怎麼走都可以,所以這樣答案與終點無關。
那麼怎麼得到這樣一條路徑呢?
顯然我們需要選定一位 \(i\)(不能是二進位制下最低的一位!),然後從出發點走遍所有邊權二進位制這一位為 \(1\) 的邊,然後我們只需要找是否存在一條邊能夠讓與和變成 \(0\)。
具體用程式碼實現的話需要利用前面建立的並查集,並且記錄每一個點所有的出邊的邊權的與和 \(f_i\),然後算出在同一個聯通塊裡面的點 \(f_i\) 的與和,如果是 \(0\) 代表這一個聯通塊內的點作為出發點可以做到答案為 \(1\)。
如果答案不是 \(0\) 也不是 \(1\),那麼答案就是 \(2\) 了。
程式碼:
#include<cstdio>
#define gc getchar
#define maxn 100039
using namespace std;
int read(){
char c=gc(); int s=0; int flag=0;
while((c<'0'||c>'9')&&c!='-') c=gc(); if(c=='-') c=gc(),flag=1;
while('0'<=c&&c<='9'){ s=(s<<1)+(s<<3)+(c^48); c=gc(); }
if(flag) return -s; return s;
}
int n,m,u,v,w,T,fa[30][maxn],f[maxn],s[maxn],flag[maxn];
int getfa(int x,int y){ if(fa[x][y]==y) return y; else return fa[x][y]=getfa(x,fa[x][y]); }
int check0(){ int i; for(i=0;i<30;i++) if(getfa(i,u)==getfa(i,v)) return 1; return 0; }
int main(){
n=read(); m=read(); int i,j,k; for(i=0;i<30;i++) for(j=1;j<=n;j++) fa[i][j]=j;
for(i=1;i<=n;i++) f[i]=(1<<30)-1;
for(i=1;i<=m;i++){
u=read(); v=read(); w=read(); f[u]&=w; f[v]&=w;
for(j=0;j<30;j++) if(w&(1<<j)) fa[j][getfa(j,u)]=getfa(j,v);
}
for(k=1;k<30;k++){
for(i=1;i<=n;i++) s[i]=(1<<30)-1;
for(i=1;i<=n;i++) s[getfa(k,i)]&=f[i];
for(i=1;i<=n;i++) if(s[getfa(k,i)]==0) flag[i]=1;
} T=read(); while(T--){
u=read(); v=read(); if(check0()) puts("0"); else if(flag[u]) puts("1"); else puts("2");
} return 0;
}