[题解] 洛谷 P3603 雪辉

模拟赛中遇到了这个题,当时我这个沙雕因为把一个\(y\)打成了\(x\)而爆零。回来重新写这道题,莫名其妙的拿了rank1。。。
我的解法与其他几位的题解有些不同我太蒻了。并没有选取所谓的关键点,而是用树链剖分将树上问题转化为序列的问题。


序列问题的解决

  • 现在的问题是,维护一个序列,要求查询若干个区间的并集的权值种类和\(mex\),无修改操作。
  • 如果只有一个区间,那么这个问题就可以很愉快的用主席树解决了。
  • 然而要查询的是区间并的权值种类和\(mex\),而且这两个东西都不满足区间可加性,于是可以放弃线段树的思路了。
  • 可以想到用分块解决这个问题,设每一块大小为\(B\)
  • 考虑到点权\(\leq 30000\),又要合并若干个区间,于是可以想到用\(bitset\)维护区间权值信息。
  • \(f[][]\)为一个\(bitset\)的二维数组,\(f[l][r]\)表示从第\(l\)块到第\(r\)块(包含\(l,r\))之间的权值集合,这个数组可以在\(O(n\frac{30000}{32})\)的时间内预处理出来。每次查询的时候,整块的信息可以在\(O(\frac{30000}{32})\)的时间内解决。
  • 剩余的部分直接暴力处理,时间复杂度为\(O(B)\)。这样单次查询可以做到\(O(\frac{30000}{32}+B)\)
  • 至于区间并,直接开一个全局变量记录答案(我的代码中用的是\(cur\)),依次处理每个区间。因为重叠的部分并不会对答案有影响。现在序列的问题已经解决了。

序列处理部分的代码

int b[N]; // 记录区间上每一个位置属于哪个块
int L[N], R[N]; // 每个块的左右断点
bitset<W> cur; // 查询时用到的全局变量
bitset<W> f[110][110]; // 预处理的 f 数组

void preWork() {
  // 预处理
  for (int i = 1; i <= n; ++i) {
    // 先计算单个块的权值情况
    b[i] = (i-1) / B + 1;
    f[b[i]][b[i]].set(a[i]);
  }
  // 处理每个块的左右端点
  for (int i = 1; i <= b[n]; ++i)
    L[i] = R[i-1] + 1, R[i] = i * B;
  R[b[n]] = n; // 最后一个块的右端点要特判
  for (int i = 1; i < b[n]; ++i)
    for (int j = i+1; j <= b[n]; ++j) // 计算 f 数组
      f[i][j] = f[i][j-1] | f[j][j];
}

void queryOnBlock(int l, int r) {
  if (b[l] == b[r]) {
    // 特判左右端点在同一个块内的情况
    for (int i = l; i <= r; ++i) cur.set(a[i]);
    return;
  }
  cur |= f[b[l]+1][b[r]-1]; // 两块之间的部分直接查询
  for (int i = l; i <= R[b[l]]; ++i) cur.set(a[i]); // 左边的剩余部分
  for (int i = L[b[r]]; i <= r; ++i) cur.set(a[i]); // 右边的剩余部分
}

int mex(bitset<W> &s) {
  // 暴力求 mex
  for (int i = 0; i < W; ++i)
    if (!s.test(i)) return i;
  return 1e9;
}

将树上问题转化为序列问题

树链剖分的板子(我这个蒟蒻写挂的部分)。。。
直接贴代码了

int G[N], ed = 1, w[N]; // 树的存储
struct Edge {
  int to, nxt;
  Edge() { to = nxt = 0; }
  Edge(int to, int nxt) : to(to), nxt(nxt) {}
} e[N<<1];

inline void addEdge(int x, int y) {
  e[++ed] = Edge(y, G[x]), G[x] = ed;
  e[++ed] = Edge(x, G[y]), G[y] = ed;
}

// 树链剖分相关
int dfn[N]; // dfs 序
int fa[N]; // 父结点
int son[N]; // 重儿子
int top[N]; // 重链顶端
int size[N]; // 子数大小
int dep[N]; // 深度
int a[N]; // 转化的序列

void dfs1(int x, int p) {
  size[x] = 1, fa[x] = p;
  for (int i = G[x]; i != 0; i = e[i].nxt) {
    int y = e[i].to;
    if (y == p) continue;
    dep[y] = dep[x] + 1;
    dfs1(y, x);
    size[x] += size[y];
    if (size[son[x]] < size[y])
      son[x] = y;
  }
}

void dfs2(int x, int t) {
  static int cur = 0;
  dfn[x] = ++cur, a[cur] = w[x], top[x] = t;
  if (!son[x]) return;
  dfs2(son[x], t);
  for (int i = G[x]; i != 0; i = e[i].nxt) {
    int y = e[i].to;
    if (y == son[x] || y == fa[x]) continue;
    dfs2(y, y);
  }
}

void queryOnTree(int x, int y) {
  // 树上查询
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]]) swap(x, y);
    queryOnBlock(dfn[top[x]], dfn[x]);
    x = fa[top[x]];
  }
  if (dfn[x] > dfn[y]) swap(x, y);
  queryOnBlock(dfn[x], dfn[y]);
}

总代码

// 2598ms 57.76MB 无O2
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <bitset>
#define LL long long
using namespace std;

inline int getint() {
  int x = 0, f = 1; char c = getchar();
  while (!isdigit(c)) { if (c == '-') f = 0; c = getchar(); }
  while (isdigit(c)) { x = (x*10) + (c-'0'); c = getchar(); }
  return f ? x : -x;
}

const int N = 1e5 + 10;
const int B = 1e3;
const int W = 30001;

int n, m, flag; // flag 判断是否需要异或
int ans1, ans2, lastans; // ans1 为权值种类,ans2 为权值 mex

int G[N], ed = 1, w[N]; // 树的存储
struct Edge {
  int to, nxt;
  Edge() { to = nxt = 0; }
  Edge(int to, int nxt) : to(to), nxt(nxt) {}
} e[N<<1];

inline void addEdge(int x, int y) {
  e[++ed] = Edge(y, G[x]), G[x] = ed;
  e[++ed] = Edge(x, G[y]), G[y] = ed;
}

// 树链剖分相关
int dfn[N]; // dfs 序
int fa[N]; // 父结点
int son[N]; // 重儿子
int top[N]; // 重链顶端
int size[N]; // 子数大小
int dep[N]; // 深度
int a[N]; // 转化的序列

void dfs1(int x, int p) {
  size[x] = 1, fa[x] = p;
  for (int i = G[x]; i != 0; i = e[i].nxt) {
    int y = e[i].to;
    if (y == p) continue;
    dep[y] = dep[x] + 1;
    dfs1(y, x);
    size[x] += size[y];
    if (size[son[x]] < size[y])
      son[x] = y;
  }
}

void dfs2(int x, int t) {
  static int cur = 0;
  dfn[x] = ++cur, a[cur] = w[x], top[x] = t;
  if (!son[x]) return;
  dfs2(son[x], t);
  for (int i = G[x]; i != 0; i = e[i].nxt) {
    int y = e[i].to;
    if (y == son[x] || y == fa[x]) continue;
    dfs2(y, y);
  }
}

int b[N]; // 记录区间上每一个位置属于哪个块
int L[N], R[N]; // 每个块的左右断点
bitset<W> cur; // 查询时用到的全局变量
bitset<W> f[110][110]; // 预处理的 f 数组

void preWork() {
  // 预处理
  for (int i = 1; i <= n; ++i) {
    // 先计算单个块的权值情况
    b[i] = (i-1) / B + 1;
    f[b[i]][b[i]].set(a[i]);
  }
  // 处理每个块的左右端点
  for (int i = 1; i <= b[n]; ++i)
    L[i] = R[i-1] + 1, R[i] = i * B;
  R[b[n]] = n; // 最后一个块的右端点要特判
  for (int i = 1; i < b[n]; ++i)
    for (int j = i+1; j <= b[n]; ++j) // 计算 f 数组
      f[i][j] = f[i][j-1] | f[j][j];
}

void queryOnBlock(int l, int r) {
  if (b[l] == b[r]) {
    // 特判左右端点在同一个块内的情况
    for (int i = l; i <= r; ++i) cur.set(a[i]);
    return;
  }
  cur |= f[b[l]+1][b[r]-1]; // 两块之间的部分直接查询
  for (int i = l; i <= R[b[l]]; ++i) cur.set(a[i]); // 左边的剩余部分
  for (int i = L[b[r]]; i <= r; ++i) cur.set(a[i]); // 右边的剩余部分
}

void queryOnTree(int x, int y) {
  // 树上查询
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]]) swap(x, y);
    queryOnBlock(dfn[top[x]], dfn[x]);
    x = fa[top[x]];
  }
  if (dfn[x] > dfn[y]) swap(x, y);
  queryOnBlock(dfn[x], dfn[y]);
}

int mex(bitset<W> &s) {
  // 暴力求 mex
  for (int i = 0; i < W; ++i)
    if (!s.test(i)) return i;
  return 1e9;
}

int main() {
  cin >> n >> m >> flag;
  for (int i = 1; i <= n; ++i) w[i] = getint();
  for (int i = 1; i < n; ++i) {
    int x = getint(), y = getint();
    addEdge(x, y);
  }
  dfs1(1, 0), dfs2(1, 1);
  preWork();
  for (int i = 1; i <= m; ++i) {
    cur.reset();
    int num = getint();
    for (int j = 1; j <= num; ++j) {
      int x = getint() ^ (flag*lastans);
      int y = getint() ^ (flag*lastans);
      queryOnTree(x, y);
    }
    ans1 = cur.count(), ans2 = mex(cur);
    lastans = ans1 + ans2;
    printf("%d %d\n", ans1, ans2);
  }
  return 0;
}

猜你喜欢

转载自www.cnblogs.com/yydyz/p/10599491.html