这道题真的让我受益匪浅,加深了对线性基的认识。
首先,考虑了一般解法,较暴力。
将x->y路径上所有的点都加入一个空的线性基集合,最后求一次最大值即可。
既然要将每个点都加入,那么必然要一个点一个点地跳。(因为每个点都要遍历到,所以不能倍增)
那么,这时最坏复杂度在N^2。只有30分。
考虑去优化。
思路1:首先,对于线性基,它是可以合并的,即将线性基A里的每个数都插入到线性基B中。
所以我们只需要做到的就是高效地合并,高效地查询。
因为是树上的合并,树剖登场。
对于线段树上的点,我们去维护它的子节点合并后的线性基。
那么我们树剖查询时,将一整条链的线性基都合并到答案中。
每次都能logn处理出一条链的点。那么复杂度就降到了nlogn^4左右。
然后就可以过了。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int,int> pii; const int N = 2e4+5; const int M = 1e4; const LL Mod = 1e9+7; #define rg register #define pi acos(-1) #define INF 1e18 #define CT0 cin.tie(0),cout.tie(0) #define IO ios::sync_with_stdio(false) #define dbg(ax) cout << "now this num is " << ax << endl; namespace FASTIO{ inline LL read(){ LL x = 0,f = 1;char c = getchar(); while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();} return x*f; } void print(int x){ if(x < 0){x = -x;putchar('-');} if(x > 9) print(x/10); putchar(x%10+'0'); } } using namespace FASTIO; void FRE(){ /*freopen("data1.in","r",stdin); freopen("data1.out","w",stdout);*/} int n,q,tim = 0,ssize[N],son[N],id[N],rk[N],top[N],fa[N],dep[N]; LL val[N],p[N]; vector<int> G[N]; struct Node{ int L,r; LL p[61]; }node[N<<2]; void dfs(int u,int ffa) { ssize[u] = 1,dep[u] = dep[ffa]+1,fa[u]= ffa; for(auto v : G[u]) { if(v == ffa) continue; dfs(v,u); ssize[u] += ssize[v]; if(ssize[v] > ssize[son[u]]) son[u] = v; } } void dfs1(int u,int sta) { top[u] = sta; id[u] = ++tim; rk[tim] = u; if(!son[u]) return ; dfs1(son[u],sta); for(auto v : G[u]) if(v != fa[u] && v != son[u]) dfs1(v,v); } void insert(LL p[],LL x) { for(rg int i = 60;i >= 0;--i) { if(((x>>i)&1) == 0) continue; if(p[i] == 0){p[i] = x;return ;} else x ^= p[i]; } } LL Get_Max() { LL ma = 0; for(rg int i = 60;i >= 0;--i) { //if(p[i]) printf("p[%d] is %lld ",i,p[i]); ma = max(ma,ma^p[i]); } return ma; } void Merge(LL p1[],LL p2[]) { for(rg int i = 60;i >= 0;--i) { if(p2[i]) insert(p1,p2[i]); } } void Pushup(int idx) { Merge(node[idx].p,node[idx<<1].p); Merge(node[idx].p,node[idx<<1|1].p); } void build(int L,int r,int idx) { node[idx].L = L,node[idx].r = r; if(L == r) { insert(node[idx].p,val[rk[L]]); return ; } int mid = (L+r)>>1; build(L,mid,idx<<1); build(mid+1,r,idx<<1|1); Pushup(idx); } void query(int L,int r,int idx) { if(node[idx].L >= L && node[idx].r <= r) { Merge(p,node[idx].p); return ; } int mid = (node[idx].L+node[idx].r)>>1; if(mid >= L) query(L,r,idx<<1); if(mid < r) query(L,r,idx<<1|1); } void tree_query(int x,int y) { while(top[x] != top[y]) { if(dep[top[x]] < dep[top[y]]) swap(x,y); query(id[top[x]],id[x],1); x = fa[top[x]]; } if(dep[x] > dep[y]) swap(x,y); query(id[x],id[y],1); } int main() { n = read(),q = read(); for(rg int i = 1;i <= n;++i) val[i] = read(); for(rg int i = 1;i < n;++i) { int u,v;u = read(),v = read(); G[u].push_back(v); G[v].push_back(u); } dfs(1,0);dfs1(1,0);build(1,n,1); while(q--) { int x,y;x = read(),y = read(); memset(p,0,sizeof(p)); tree_query(x,y); LL ans = Get_Max(); printf("%lld ",ans); } system("pause"); } /* 6 10 1 3 11 5 7 9 1 2 2 4 2 5 1 3 3 6 */
思路2:我们可以发现,对于我们想要查询的点,我们必定要保证它在x->y的路径上才行。
那么我们用p[i][j]来表示1->i的线性基。然后从父节点往下转移即可。然后我们保证j位置上的线性基的深度尽量大,这样就能使线性基尽量在链上
每次都插入i点去更新线性基。
注意的是,这里更新线性基,不引入参数数组,就会很难写。
对于j位置如果当前的点深度更大,那么j位置的线性基显然要更新为当前点的价值。
那么当前点的价值显然不能再去更新低点(就像一开始没有值一样)
此时我们的值就应该变为原来的j位置的线性基(因为它现在被挤下来了),所以用它去更新。(这里也是最麻烦的地方)
然后就对于x,y都进行一次线性基的更新即可。
// Author: levil #include<bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int,int> pii; const int N = 2e4+5; const int M = 1e4; const LL Mod = 1e9+7; #define rg register #define pi acos(-1) #define INF 1e18 #define CT0 cin.tie(0),cout.tie(0) #define IO ios::sync_with_stdio(false) #define dbg(ax) cout << "now this num is " << ax << endl; namespace FASTIO{ inline LL read(){ LL x = 0,f = 1;char c = getchar(); while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();} return x*f; } void print(int x){ if(x < 0){x = -x;putchar('-');} if(x > 9) print(x/10); putchar(x%10+'0'); } } using namespace FASTIO; void FRE(){ /*freopen("data1.in","r",stdin); freopen("data1.out","w",stdout);*/} LL v[N],p[N][65];//1~i位置对应的线性基。 vector<int> G[N]; int dep[N],lg[N],f[N][30],pos[N][65];//pos记录线性基的位置,确保深度最大 void init() { lg[0] = 1; for(rg int i = 1;i < N;++i) lg[i] = lg[i-1] + ((1<<lg[i-1]) == i); } void update(int u,LL p[],int pos[]) { LL x = v[u]; for(rg int i = 61;i >= 0;--i) { if(((x>>i)&1) == 0) continue; if(!p[i]) { p[i] = x;pos[i] = u; return ; } if(dep[u] > dep[pos[i]]) { swap(pos[i],u); swap(x,p[i]); } x ^= p[i]; } } void dfs(int u,int fa) { dep[u] = dep[fa]+1,f[u][0] = fa; for(rg int i = 1;i <= lg[dep[u]];++i) f[u][i] = f[f[u][i-1]][i-1]; for(rg int i = 61;i >= 0;--i) p[u][i] = p[fa][i],pos[u][i] = pos[fa][i]; update(u,p[u],pos[u]); for(auto v : G[u]) if(v != fa) dfs(v,u); } int LCA(int x,int y) { if(dep[x] < dep[y]) swap(x,y); while(dep[x] > dep[y]) x = f[x][lg[dep[x]-dep[y]]-1]; if(x == y) return x; for(rg int i = lg[dep[x]]-1;i >= 0;--i) if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i]; return f[x][0]; } //CF1100F Ivan and Burgers int main() { init(); int n,q;n = read(),q = read(); for(rg int i = 1;i <= n;++i) v[i] = read(); for(rg int i = 1;i < n;++i) { int x,y;x = read(),y = read(); G[x].push_back(y); G[y].push_back(x); } dfs(1,0); while(q--) { int x,y;x = read(),y = read(); int lca = LCA(x,y); LL f[65]; for(rg int i = 61;i >= 0;--i) { if(dep[pos[x][i]] >= dep[lca]) f[i] = p[x][i]; else f[i] = 0; } for(rg int i = 61;i >= 0;--i) { if(dep[pos[y][i]] >= dep[lca]) { LL val = p[y][i]; for(rg int j = i;j >= 0;--j) { if(((val>>j)&1) == 0) continue; if(f[j] == 0){f[j] = val;break;} val ^= f[j]; } } } LL ans = 0; for(rg int i = 61;i >= 0;--i) { ans = max(ans,ans^f[i]); } printf("%lld ",ans); } system("pause"); } /* 6 10 1 3 11 5 7 9 1 2 2 4 2 5 1 3 3 6 */
总结:两种方法相对来说树剖虽然代码多了点,但是非常容易理解。
过了下思路,自己就写出来了。。(树剖日常忘rk放上线段树。。)
第二种方法相对还是要比树剖快点。大概两个log吧。。
其实还有第三种思路。
那就是从点分治。。(orz)