題目:給定 n 節點樹與 m 操作:
C a b c:將路徑 a→b 所有節點染色 cQ a b:查詢路徑 a→b 上顏色段數量(連續相同顏色算一段)O(log n) 條重鏈區間lc(最左顏色)、rc(最右顏色)、sum(顏色段數)、lazy(區間染色標記)
struct Node {
int lc, rc; // 區間最左/最右顏色
int sum; // 區間內顏色段數
int lazy; // -1 表示無標記,>=0 表示整段染該顏色
};
void maintain(int o) {
lc[o] = lc[lson];
rc[o] = rc[rson];
sum[o] = sum[lson] + sum[rson] - (rc[lson] == lc[rson]); // 相鄰同色合併
}
void apply(int o, int color) {
lc[o] = rc[o] = color;
sum[o] = 1;
lazy[o] = color;
}
void pushdown(int o) {
if (lazy[o] != -1) {
apply(lson, lazy[o]);
apply(rson, lazy[o]);
lazy[o] = -1;
}
}
// 修改路徑 a-b 顏色
void modify_path(int x, int y, int color) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
update(1, 1, n, dfn[top[x]], dfn[x], color);
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
update(1, 1, n, dfn[x], dfn[y], color);
}
// 查詢路徑 a-b 顏色段數
int query_path(int x, int y) {
int res = 0, lcol = -1, rcol = -1;
vector<pair<int,int>> segs; // 存儲分解出的區間 [l, r]
while (top[x] != top[y]) {
if (dep[top[x]] > dep[top[y]]) {
segs.push_back({dfn[top[x]], dfn[x]});
x = fa[top[x]];
} else {
segs.push_back({dfn[top[y]], dfn[y]});
y = fa[top[y]];
}
}
if (dep[x] > dep[y]) swap(x, y);
segs.push_back({dfn[x], dfn[y]});
// 合併所有區間結果(需注意順序與端點顏色)
// 此處簡化:實際需將區間按路徑順序排列,逐段合併
}
/**************************************************************
Problem: 2243 User: Rainbow6174 Language: C++ Result: Accepted
Time:9056 ms Memory:20356 kb
**************************************************************/
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#define lson (o<<1)
#define rson ((o<<1)|1)
using namespace std;
const int maxn = 100005;
int n, m, u, v, x, tot;
int c[maxn], fa[maxn], son[maxn], sz[maxn], dep[maxn], top[maxn], dfn[maxn], rdfn[maxn];
int lc[maxn*4], rc[maxn*4], sum[maxn*4], lazy[maxn*4];
vector<int> g[maxn];
char op[5];
void dfs1(int u, int f) { ... } // 計算 sz, son, dep, fa
void dfs2(int u, int tp) { ... } // 分配 dfn, top
void build(int o, int l, int r) { ... }
void update(int o, int l, int r, int L, int R, int color) { ... }
Node query(int o, int l, int r, int L, int R) { ... } // 返回 {lc, rc, sum}
int query_path(int x, int y) { ... } // 合併多段查詢結果
void modify_path(int x, int y, int color) { ... }
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
for (int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
build(1, 1, n);
while (m--) {
scanf("%s", op);
if (op[0] == 'C') {
scanf("%d%d%d", &u, &v, &x);
modify_path(u, v, x);
} else {
scanf("%d%d", &u, &v);
printf("%d\n", query_path(u, v));
}
}
return 0;
}
| 難點 | 解決方案 |
|---|---|
| 路徑分解後區間順序顛倒 | 查詢時收集所有區間,按路徑實際順序排列再合併 |
| 相鄰區間端點顏色合併判斷 | 合併時檢查 rc[左區間] == lc[右區間],相同則段數 -1 |
| 顏色值可能為 0 | 初始化 lazy = -1,避免與顏色 0 混淆 |
| 線段樹查詢返回結構體 | 定義 struct Node {int lc, rc, sum;} 便於合併 |
O(n)O(log² n)(O(log n) 條鏈 × O(log n) 線段樹操作)O((n + m) log² n)此題是樹鏈剖分 + 線段樹維護路徑資訊的經典範本。關鍵在於:
dfn 映射代碼量較大,建議建立個人範本庫,包含 TreeChain、SegTree 兩個可復用類別。