qoj18434 I Will Always Remember You

给你一个 \(n\) 个点 \(m\) 条边的 DAG,点带颜色,有 \(q\) 次操作和查询

  • 修改某个点的颜色
  • 查询点 \(x\)DAG 上可达的点的颜色种类数。

\[n,q\le 1.5\times10^5, m\le 3\times10^5 \]


对于这种 DAG 可达性问题我们一般直接考虑 bitset

那么有一个很显然的想法就是我们令 \(f(i,j)\) 表示点 \(i\) 能否到达 \(j\)\(g(i,j)\) 表示 \(i\) 能否到达颜色为 \(j\) 的点。

那么在拓扑序上转移 \(f\),同时在每个点用 \(c_i\) 转移 \(g\) 即可做到静态回答询问。

对操作序列分块,处理一个块内的询问时把块内即将要修改的点先扣掉,即不令其更新 \(g\) 状态。

然后扫一遍块内的操作,在回答时枚举每一个被扣掉的点利用 \(f\) 更新 \(g\)

这样即可做到 \(\mathcal O(\frac{nq}\omega)\)


但不幸的是空间存不下。

我们考虑交换两维,设 \(f(i,j)\) 表示 \(i\) 能否被 \(j\) 抵达,\(g(i,j)\) 表示颜色 \(i\) 能否被 \(j\) 抵达。

这里会发现只有询问中的 \(j\) 是有用的。那么我们将其重标号,即可将第二位长度压缩至 \(B\)

但还有一个问题是我们对 \(j\) 一维压位,但实际上需要对 \(i\) 这一维求和。

直觉上我们可以直接将每个 \(g(*,j)\) 加起来,但这样会产生进位。

可以通过维护其二进制分解 \(\sum g(*,j)=\sum 2^kg(k,j)\) 做到 \(\mathcal O(\frac{nm\log n}\omega)\) 每次求和。

更好的办法是,对 \(a,b,c\in[0,1]\),我们有:( \(\oplus\)\(\text{xor}\)

\[\begin{align*} a+b+c&=s+2c \\ s&=a\oplus b\oplus c \\ c&=(a\mathbin\&b)\mathbin|(b\mathbin\&c)\mathbin|(c\mathbin\&a) \end{align*} \]

通过维护位权,可以将 \((a,b,c)\) 三个数变为 \((s,c)\) 两个数,具体可以参考代码。

删去一个数仅需 \(\mathcal O(1)\) 次操作,因此可以在 \(\mathcal O(n)\) 的时间内计算。


最优解(?

#include <algorithm>
#include <iostream>
#include <string.h>
#include <bitset>
#include <queue>

const int N = 150007;

int n, m, a[N];
struct node { int y, x; } q[N];

std::basic_string<int> g[N], gv[N];
int deg[N], topo[N];
void toposort() {
  std::queue<int> q;
  for(int i = 1; i <= n; ++i) {
    if(!deg[i]) q.push(i);
  }
  int ind = 0;
  while(!q.empty()) {
    int u = q.front(); q.pop();
    topo[++ind] = u;
    for(auto& v: gv[u]) {
      if(!--deg[v]) {
        q.push(v);
      }
    }
  }
}

const int B = 1536;
typedef std::bitset<B> BS;

int ans[N], t1[N];
std::basic_string<int> T1;
BS Rc[N], Rv[N];

int res[B];
void convolution(BS* p) {
  int weight = 0, k = n;
  while(k > 0) {
    int pt = 0, las = 0;
    for(int i = 1; i + 2 <= k; i += 2) {
      BS &a = p[i], &b = p[i+1], &c = p[i+2];
      BS x = a ^ b;
      p[++pt] = (a & b) | (x & c);
      c ^= x; las = i + 2;
    }
    if(k % 2 == 0) {
      BS x = p[k-1] ^ p[k];
      p[++pt] = p[k-1] & p[k];
      p[k] = x;
    }
    for(int i = 0; i < B; ++i)
      res[i] |= int(p[k][i]) << weight;
    ++weight, k = pt;
  }
}

inline void work(int l, int r) {
  for(int i = 1; i <= n; ++i) {
    Rc[i].reset(), Rv[i].reset();
  }
  int m2 = 0;
  T1.clear();
  for(int i = l; i <= r; ++i) {
    int x = q[i].x;
    if(q[i].y) {
      if(!t1[x]++) T1 += x;
    } else Rv[x][m2++] = 1;
  }
  for(int j = n; j >= 1; --j) {
    int i = topo[j];
    if(!t1[i]) Rc[a[i]] |= Rv[i];
    for(int& v: g[i]) Rv[v] |= Rv[i];
  }
  m2 = 0;
  for(int i = l; i <= r; ++i) {
    if(q[i].y) a[q[i].x] = q[i].y;
    else {
      int x = q[i].x, ind = m2++;
      for(auto& y: T1) {
        if(Rv[y][ind] && !Rc[a[y]][ind]) {
          Rc[a[y]][ind] = 1;
        }
      }
    }
  }
  for(auto& x: T1) t1[x] = 0;
  for(int i = 0; i < m2; ++i) res[i] = 0;
  convolution(Rc);
  m2 = 0;
  for(int i = l; i <= r; ++i) 
    if(!q[i].y) ans[i] = res[m2++];
}

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  std::cin >> n >> m;
  for(int i = 1; i <= n; ++i) std::cin >> a[i];
  for(int i = 1; i <= m; ++i) {
    int x, y; std::cin >> x >> y;
    g[x] += y, ++deg[x], gv[y] += x;
  }
  toposort();
  std::cin >> m;
  for(int i = 1; i <= m; ++i) {
    int op; std::cin >> op;
    if(op == 1) std::cin >> q[i].x >> q[i].y;
    else std::cin >> q[i].x;
  }
  int q1 = 0, q2 = 0, las = 0;
  for(int i = 1; i <= m; ++i) {
    ++(q[i].y ? q1: q2);
    if(i == m || q2 >= B || q1 >= B) {
      work(las + 1, i);
      las = i, q1 = q2 = 0;
    }
  }
  for(int i = 1; i <= m; ++i) 
    if(!q[i].y) std::cout << ans[i] << "\n";
}
posted @ 2026-08-19 03:52  CuteNess  阅读(13)  评论(0)    收藏  举报