BZOJ 4568 [Scoi2016]幸运数字

题目大意:给你一颗树,多个询问,问你树上任意两点的路径上选任意几个点使得异或和最大。

我是参考的claris大神的代码(%%%),点分治,对于询问在两个子树间或者有一个在重心上的进行回答,否则把问题用链表接到询问点所在的子树上。
具体方法可以选中重心都对每个子树染色,染为这个子树的根节点。在子树处理问题之前一定要记住先把询问拖出来,然后把子树的问题清零。因为你选的是这个子树的重心开始分治,而不是根,所以这个子树的根可能就会又加入一些问题,你不清零就会重复计算一些问题和出错。
然后是对于重心到子树每个点都求一次线性基,最后暴力合并在不同子树两点的线性基就是答案(claris的线性基也是%%%%),具体可参照代码

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N = 20010, M = 40010, Q = 200010*30, E = 200010;
int n, m, g[N], to[M], nxt[M], ok[M], np, G[N], To[Q], Nxt[Q], Np, now, all, son[N], color[N], f[N];
ll w[N], Ans[E];
struct data{ int x,y; }q[E];
struct gauss {
  ll a[65];
  gauss(){ for(int i=0;i<60;i++) a[i] = 0; }
  inline void ins(ll x) { for(int i=59;~i;i--) if((x>>i)&1) { if(a[i]) x^=a[i]; else { a[i]=x; break; } } }
  inline ll ask() {
    ll ans = 0;
    for(int i=59;~i;i--) if((ans^a[i]) > ans) ans ^= a[i];
    return ans;
  }
}h[N], tmp;
template<class T> 
inline void read(T &x) {
 char op; while( ((op=getchar())<'0') || (op>'9'));
 x = op-'0'; while(((op=getchar())>='0') && (op<='9')) (x*=10)+=op-'0'; 
}
char buf[30];
template<class T>
inline void out(T x) {
  int i = 0;
  if(!x) putchar('0');
  while(x) {buf[++i] = x%10+'0'; x/=10;}
  while(i) putchar(buf[i--]);
  putchar('\n');
}

inline void push(int x,int y) { nxt[++np] = g[x]; to[np] = y; g[x] = np; ok[np] = 1; }
inline void PUSH(int x,int y) { Nxt[++Np] = G[x]; To[Np] = y; G[x] = Np;}

void findG(int x, int Fa) {
  f[x] = 0;
  for(int i=g[x];i;i=nxt[i]) 
    if(ok[i] && to[i] != Fa) {
      int y = to[i];
      findG(y, x);
      f[x] = f[x] < son[y] ? son[y] : f[x]; 
    }
  f[x] = f[x] < (all-son[x]) ? (all-son[x]) : f[x];
  if(f[now] > f[x]) now = x;
}

void Paint(int x,int Fa,int c) {
  color[x] = c;
  son[x] = 1;
  h[x] = h[Fa];
  h[x].ins(w[x]);
  for(int i=g[x];i;i=nxt[i]) 
    if(ok[i] && to[i] != Fa) {
      int y = to[i];
      Paint(y,x,c);
      son[x] += son[y];
    }
}

void work(int x) {
  if(!G[x]) return;
  f[0] = all = son[x]; 
  if(x!=1) findG(x,now=0);
  else now = 1;
  h[now] = gauss();
  h[now].ins(w[now]);
  for(int i=g[now];i;i=nxt[i]) if(ok[i]) Paint(to[i],now,to[i]);
  int t = G[x]; G[x] = 0;
  for(;t;t=Nxt[t]) {
    int i = To[t];
    if(q[i].x == now || q[i].y == now || color[q[i].x] != color[q[i].y]) {
      tmp = h[q[i].x];
      for(int j=59;~j;j--) 
        if(h[q[i].y].a[j])tmp.ins(h[q[i].y].a[j]);
      Ans[i] = tmp.ask();
    }
    else PUSH(color[q[i].x],i);
  }
  for(int i=g[now];i;i=nxt[i]) if(ok[i]) { ok[i^1] = 0; work(to[i]); }
}

int main() {
  memset(g,0,sizeof(g));
  memset(G,0,sizeof(G));
  read(n);read(m);
  int x,y;
  for(int i=1;i<=n;i++) read(w[i]);
  np = Np = 1;
  for(int i=1;i<n;i++) {
    read(x);read(y);
    push(x,y);
    push(y,x);
  }
  for(int i=1;i<=m;i++) read(q[i].x), read(q[i].y);
  for(int i=1;i<=m;i++){ 
    if(q[i].x == q[i].y) Ans[i] = w[q[i].x];
    else PUSH(1,i);
  }
  work(1);
  for(int i=1;i<=m;i++) out(Ans[i]);
  return 0; 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值