java提高篇(三)-----理解java的三大特性之多型
目錄
樹鏈剖分
1.演算法分析
dfs1函式:這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
1.1 重鏈剖分:
1.1.1 定義
重子節點 表示其子節點中子樹最大的子結點。如果有多個子樹最大的子結點,取其一。如果沒有子節點,就無重子節點。輕子節點
1.1.2 重鏈剖分的性質
樹上每個節點都屬於且僅屬於一條重鏈。 重鏈開頭的結點不一定是重子節點(因為重邊是對於每一個結點都有定義的)。所有的重鏈將整棵樹 完全剖分 。在剖分時 優先遍歷重兒子 ,最後重鏈的DFS序就會是連續的。在剖分時,重邊優先遍歷,最後樹的 DFN 序上,重鏈內的DFN序是連續的。按DFN排序後的序列即為剖分後的鏈。一顆子樹內的 DFN 序是連續的。可以發現,當我們向下經過一條 輕邊 時,所在子樹的大小至少會除以二。因此,對於樹上的任意一條路徑,把它拆分成從 分別向兩邊往下走,分別最多走O(logn)次,因此,樹上的每條路徑都可以被拆分成不超過 O(logn)
1.1.3 常見應用
- 路徑維護:用樹鏈剖分求樹上兩點路徑權值和。鏈上的 DFS 序是連續的,可以使用線段樹、樹狀陣列維護。每次選擇深度較大的鏈往上跳,直到兩點在同一條鏈上。同樣的跳鏈結構適用於維護、統計路徑上的其他資訊。
- 子樹維護:維護子樹上的資訊,譬如將以x為根的子樹的所有結點的權值增加v。在 DFS 搜尋的時候,子樹中的結點的 DFS 序是連續的。每一個結點記錄 bottom 表示所在子樹連續區間末端的結點。這樣就把子樹資訊轉化為連續的一段區間資訊。
- 求最近公共祖先:不斷向上跳重鏈,當跳到同一條重鏈上時,深度較小的結點即為 LCA。
1.2 長鏈剖分
1.2.1 定義
長鏈剖分本質上就是另外一種鏈剖分方式。
定義 重子節點
定義 輕子節點 表示剩餘的子結點。
從這個結點到重子節點的邊為 重邊 。
到其他輕子節點的邊為 輕邊 。
若干條首尾銜接的重邊構成 重鏈 。
把落單的結點也當作重鏈,那麼整棵樹就被剖分成若干條重鏈。
如圖(這種剖分方式既可以看成重鏈剖分也可以看成長鏈剖分):
1.2.2 長鏈剖分性質
- 任意點祖先所在長鏈長度一點大於等於這個點所在長鏈長度
- 所有長鏈長度之和就是總結點數
- 一個點到根節點的路徑上經過的短邊最多有 √n 條 (長兒子深度和短兒子深度相等時取到)
1.2.3 長鏈剖分應用
- O(1) 移動到鏈頭 (求lca,和重鏈剖分一樣)
- O(nlogn) 預處理,單次 O(1) 線上查詢一個點的 k 級祖先
- O(n) 處理可合併的與深度有關的子樹資訊 (例如某深度點數、某深度點權和)
2. 板子
2.1 重鏈剖分
2.1.1 路徑維護/子樹維護/lca
luogu P3384 【模板】輕重鏈剖分
一棵包含 N 個結點的樹(連通且無環),每個節點上包含一個數值,需要支援以下操作:
操作 1: 格式: 1 x y z 表示將樹從 x 到 y 結點最短路徑上所有節點的值都加上 z。
操作 2: 格式: 2 x y 表示求樹從 x 到 y 結點最短路徑上所有節點的值之和。
操作 3: 格式: 3 x z 表示將以 x 為根節點的子樹內所有節點值都加上 z。
操作 4: 格式: 4 x 表示求以 x 為根節點的子樹內所有節點值之和
操作有1e5個,樹的節點數為1e5個
#include<bits/stdc++.h>
using namespace std;
const int N=100000+10;
int tot, num;
int n, m, r, p;
int w[N], a[N], dat[N*4], lazy[N*4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N*2], ne[N*2], idx; // 鄰接表陣列
int dep[N], dfn[N], wson[N], size[N], top[N], fa[N]; // dep深度 dfn搜尋序 wson重兒子 size子樹大小 top鏈頭 fa父節點
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 得到size, fa, dep, wson陣列
void dfs1(int u)
{
dep[u]=dep[fa[u]]+1;
size[u]=1;
for(int i=h[u]; ~i; i=ne[i])
{#include<bits/stdc++.h>
using namespace std;
const int N = 100000+10;
int tot, num;
int n, m, r, p;
int w[N], a[N], dat[N * 4], lazy[N * 4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N * 2], ne[N * 2], idx; // 鄰接表陣列
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; // dep深度 dfn搜尋序 wson重兒子 size子樹大小 top鏈頭 fa父節點
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 得到sz, fa, dep, wson陣列
void dfs1(int u)
{
dep[u] = dep[fa[u]]+1;
sz[u] = 1;
for(int i = h[u]; ~i; i = ne[i])
{
int j=e[i];
if(j == fa[u]) continue;
fa[j] = u;
dfs1(j);
sz[u] += sz[j];
if(sz[j] > sz[wson[u]]) wson[u] = j; // 這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
}
}
// 得到dfn, top陣列
void dfs2(int u, int nowtop)
{
dfn[u] = ++num;
w[num] = a[u];
//以搜尋序重排權值
top[u] = nowtop;
if(wson[u]) dfs2(wson[u], nowtop); // 先搜尋重兒子
for(int i = h[u]; ~i; i = ne[i]) // 然後搜尋輕兒子
{
int y=e[i];
if(y ==fa[u]||y == wson[u]) continue;
dfs2(y, y);
}
}
void pushup(int rt) {
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
dat[rt] %= p;
}
// 建線段樹,rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界
void build(int rt, int l, int r)
{
if(l==r)
{
dat[rt]=w[l];
return ;
}
int mid=(l + r)>>1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid+1, r);
pushup(rt);
}
// 下傳
void pushdown(int rt, int l, int r)
{
if(lazy[rt])
{
int mid=(l + r)>>1;
dat[rt << 1] += lazy[rt]*(mid - l + 1), dat[rt << 1] %= p, lazy[rt << 1] += lazy[rt];
dat[rt << 1 | 1] += lazy[rt]*(r-mid), dat[rt << 1 | 1] %= p, lazy[rt << 1 | 1] += lazy[rt];
lazy[rt]=0;
}
}
// rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界, l為需要修改的左區間,r為需要修改的右區間
void modify(int rt, int l, int r, int L, int R, int k)
{
if(L <= l && r <= R)
{
dat[rt] += k*(r-l+1);
dat[rt] %= p;
lazy[rt] += k;
lazy[rt] %= p;
return ;
}
pushdown(rt, l, r);
int mid = (l + r)>>1;
if(L <= mid) modify(rt << 1, l, mid, L, R, k);
if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, k);
pushup(rt);
}
// rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界, l為需要查詢的左區間,r為查詢的右區間
int query(int rt, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
return dat[rt];
}
pushdown(rt, l, r);
int mid = (l + r)>>1;
int ans = 0;
if(L <= mid) ans += query(rt << 1, l, mid, L, R), ans %= p;
if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R), ans %= p;
return ans;
}
// 求樹從 x 到 y 結點最短路徑上所有節點的值之和
int path_query_Tree(int x, int y)
{
//兩點間的修改
int ans = 0;
while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x點為深的那個點
ans += query(1, 1, n, dfn[top[x]], dfn[x]);
ans %= p;
x = fa[top[x]]; // x每次跳一條鏈
}
if(dep[x] > dep[y]) swap(x, y); // 讓x成為深度更淺的那個點
ans += query(1, 1, n, dfn[x], dfn[y]);
return ans % p;
}
// 將樹從 x到 y 結點最短路徑上所有節點的值都加上 z
void path_modify_Tree(int x, int y, int k)
{
//樹上兩點距離
k %= p;
while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x成為對應的頭部深度更大的那個點
modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子樹和
x = fa[top[x]]; // x跳到原來x的頭部的父節點
}
if(dep[x] > dep[y]) swap(x, y); // 讓x成為深度更淺的那個點
modify(1, 1, n, dfn[x], dfn[y], k);
}
// 求以 x 為根節點的子樹內所有節點值之和
int Point_query_Tree(int rt)
{
//由搜尋序的特點可得,子樹的搜尋序一定比根大
return query(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1);
}
// 將以 x 為根節點的子樹內所有節點值都加上 z
void Point_modify_Tree(int rt, int k)
{
k %= p;
modify(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1, k);
}
// 求u和v的lca
int lca(int u, int v)
{
while (top[u] != top[v])
{
if (dep[top[u]] > dep[top[v]])
u = fa[top[u]];
else
v = fa[top[v]];
}
return dep[u] > dep[v] ? v : u;
}
int main()
{
scanf("%d%d%d%d", &n, &m, &r, &p); // 讀入點數、邊數、根、模數
for(int i=1; i<=n; i++) scanf("%d", &a[i]); // 讀入每個點的權值
// 讀入邊,建樹
memset(h, -1, sizeof h);
for(int i=1, x, y; i<n; i++)
{
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
// 兩次dfs把樹按照重鏈剖分
dfs1(r); // 得到sz, fa, dep, wson陣列
dfs2(r, r); // 得到dfn, top陣列
build(1, 1, n);
// m次詢問
for(int i=1, op, x, y, z; i<=m; i++)
{
scanf("%d", &op);
if(op == 1)
{
scanf("%d%d%d", &x, &y, &z);
path_modify_Tree(x, y, z); // 將樹從 x到 y 結點最短路徑上所有節點的值都加上 z。
}
else if(op == 2)
{
scanf("%d%d", &x, &y);
printf("%d\n", path_query_Tree(x, y)); //求樹從 x 到 y 結點最短路徑上所有節點的值之和
}
else if(op == 3)
{
scanf("%d%d", &x, &z);
Point_modify_Tree(x, z); // 將以 x 為根節點的子樹內所有節點值都加上 z
}
else
{
scanf("%d", &x);
printf("%d\n", Point_query_Tree(x)); //求以 x 為根節點的子樹內所有節點值之和
}
}
return 0;
}
lugou P2486 [SDOI2011]染色
題意:
給定一棵 n 個節點的無根樹,共有 m 個操作,操作分為兩種:
- 將節點 aa 到節點 bb 的路徑上的所有點(包括 aa 和 bb)都染成顏色 cc。
- 詢問節點 aa 到節點 bb 的路徑上的顏色段數量。
顏色段的定義是極長的連續相同顏色被認為是一段。例如 112221 由三段組成:11、222、1。
題解: 線段樹+樹剖,線段樹維護顏色資訊即可
以下程式碼沒有調通
#include<bits/stdc++.h>
using namespace std;
const int N=1e5 + 10;
int tot, num;
int n, m, r;
int w[N], a[N], lco[N * 4], rco[N * 4], dat[N*4], lazy[N*4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N*2], ne[N*2], idx; // 鄰接表陣列
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; // dep深度 dfn搜尋序 wson重兒子 sz子樹大小 top鏈頭 fa父節點
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 得到sz, fa, dep, wson陣列
void dfs1(int u)
{
dep[u] = dep[fa[u]] + 1;
sz[u] = 1;
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == fa[u]) continue;
fa[j] = u;
dfs1(j);
sz[u] += sz[j];
if(sz[j] > sz[wson[u]]) wson[u]=j; // 這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
}
}
// 得到dfn, top陣列
void dfs2(int u, int nowtop)
{
dfn[u] = ++num;
w[num] = a[u];
//以搜尋序重排權值
top[u] = nowtop;
if(wson[u]) dfs2(wson[u], nowtop); // 先搜尋重兒子
for(int i = h[u]; ~i; i = ne[i]) // 然後搜尋輕兒子
{
int y = e[i];
if(y == fa[u] || y == wson[u]) continue;
dfs2(y, y);
}
}
void pushup(int rt) {
lco[rt] = lco[rt << 1] , rco[rt] = lco[rt << 1 | 1];
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
if (rco[rt << 1] == lco[rt << 1 | 1]) dat[rt]--;
}
// 建線段樹,rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界
void build(int rt, int l, int r)
{
if(l == r)
{
dat[rt] = 1;
lco[rt] = rco[rt] = w[l];
lazy[rt] = 0;
return ;
}
int mid = (l + r) >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
pushup(rt);
}
void pushdown(int rt, int l, int r) {
if (lazy[rt]) {
lazy[rt << 1] = lazy[rt];
lazy[rt << 1 | 1] = lazy[rt];
lco[rt << 1] = rco[rt << 1] = lazy[rt];
lco[rt << 1 | 1] = rco[rt << 1 | 1] = lazy[rt];
dat[rt << 1] = 1;
dat[rt << 1 | 1] = 1;
lazy[rt] = 0;
}
}
void modify(int rt, int l, int r, int L, int R, int c) {
if (L <= l && r <= R) {
dat[rt] = 1;
lco[rt] = rco[rt] = c;
lazy[rt] = c;
return;
}
pushdown(rt, l, r);
int mid = (l + r) >> 1;
if (L <= mid) modify(rt << 1, l, mid, L, R, c);
if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, c);
pushup(rt);
}
int query(int rt, int l, int r, int L, int R) {
if (L <= l && r <= R) return dat[rt];
pushdown(rt, l, r);
int mid = (l + r) >> 1;
int res = 0, flg = 0;
if (L <= mid) {
res = query(rt << 1, l, mid, L, R), flg = 1;
}
if (mid < R) {
res += query(rt << 1 | 1, mid + 1, r, L, R);
if (flg == 1 && rco[rt << 1] == lco[rt << 1 | 1]) res--;
}
return res;
}
// 求樹從 x 到 y 結點最短路徑上所有節點的值之和
int path_query_Tree(int x, int y)
{
//兩點間的修改
int ans = 0;
while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x點為深的那個點
ans += query(1, 1, n, dfn[top[x]], dfn[x]);
if (a[top[x]] == a[fa[top[x]]]) ans--;
x = fa[top[x]]; // x每次跳一條鏈
}
if(dep[x] > dep[y]) swap(x, y); // 讓x成為深度更淺的那個點
ans += query(1, 1, n, dfn[x], dfn[y]);
return ans;
}
// 將樹從 x到 y 結點最短路徑上所有節點的值都加上 z
void path_modify_Tree(int x, int y, int k)
{
//樹上兩點距離
while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x成為對應的頭部深度更大的那個點
modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子樹和
x = fa[top[x]]; // x跳到原來x的頭部的父節點
}
if(dep[x] > dep[y]) swap(x, y); // 讓x成為深度更淺的那個點
modify(1, 1, n, dfn[x], dfn[y], k);
}
int main()
{
scanf("%d%d", &n, &m); // 讀入點數、邊數、根、模數
r = 1;
for(int i = 1; i <= n; i++) scanf("%d", &a[i]); // 讀入每個點的權值
// 讀入邊,建樹
memset(h, -1, sizeof h);
for(int i = 1, x, y; i < n; i++)
{
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
// 兩次dfs把樹按照重鏈剖分
dfs1(r); // 得到sz, fa, dep, wson陣列
dfs2(r, r); // 得到dfn, top陣列
build(1, 1, n);
// m次詢問
string op;
for(int i = 1, x, y, z; i <= m; i++)
{
cin >> op;
if (op[0] == 'Q') {
scanf("%d %d", &x, &y);
printf("%d\n", path_query_Tree(x, y));
}
else {
scanf("%d%d%d", &x, &y, &z);
path_modify_Tree(x, y, z);
}
}
return 0;
}
2.2 長鏈剖分
2.2.1 求lca/求每條長鏈的長度
求lca
#include<bits/stdc++.h>
using namespace std;
const int N=500000+10;
int lson[N], maxlen[N], fa[N], dep[N], top[N]; // lso長兒子,maxlen最大深度,fa父節點,dep深度,top鏈頭
int h[N],e[N*2],ne[N*2], idx;
int n, m, root;
void add(int a,int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 求fa,dep, maxlen
void dfs1(int x)
{
dep[x]=dep[fa[x]]+1;
maxlen[x]=dep[x];
for(int i=h[x];~i;i=ne[i])
{
int y=e[i];
if(y!=fa[x])
{
fa[y]=x;
dfs1(y);
maxlen[x]=max(maxlen[y],maxlen[x]);
if(maxlen[y]>maxlen[lson[x]]) lson[x]=y; // 這裡要注意根節點不能設為0,否則根節點的最長鏈無法更新,始終為0
}
}
}
// 求top
void dfs2(int x,int nowtop)
{
top[x]=nowtop;
if(lson[x]) dfs2(lson[x],nowtop);
for(int i=h[x];~i;i=ne[i])
{
int y=e[i];
if(y!=fa[x]&&y!=lson[x]) dfs2(y,y);
}
}
// 求lca
int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int main()
{
scanf("%d%d%d",&n,&m,&root);
memset(h, -1, sizeof h);
for(int i=1,x,y;i<n;++i)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs1(root); // 第一次求dep, fa, maxlen, lson
dfs2(root,root); // 第二次求top
for(int i=1,x,y;i<=m;++i)
{
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
Benelux Algorithm Programming Contest 2019
A. Appeal to the Audience
題意: 給定一棵n點樹,該樹有m葉,每片葉子都有權值k,每片葉子能帶來葉子所處的長鏈的長度*葉子權值k的收益,求最大收益。
題解: 長鏈的貪心性質
程式碼:
/*
本題需要讓越大權值的運動員位於越長的鏈的底部,即可求出最大答案
只需要多維護一個數組len,記錄每條鏈的長度即可
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100000+10;
int lson[N], maxlen[N], fa[N], dep[N], top[N]; // lso長兒子,maxlen最大深度,fa父節點,dep深度,top鏈頭
int h[N],e[N*2],ne[N*2], idx;
int n, m, root;
int a[N], len[N]; // len維護每一條鏈的長度
void add(int a,int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 求fa,dep, maxlen
void dfs1(int x)
{
dep[x]=dep[fa[x]]+1;
maxlen[x]=dep[x];
for(int i=h[x];~i;i=ne[i])
{
int y=e[i];
if(y!=fa[x])
{
fa[y]=x;
dfs1(y);
maxlen[x]=max(maxlen[y],maxlen[x]);
if(maxlen[y]>maxlen[lson[x]]) lson[x]=y;
}
}
}
// 求top
void dfs2(int x,int nowtop, int length)
{
top[x]=nowtop;
len[nowtop] = length + 1; // 鏈頭為nowtop的鏈的長度為length+1
if(lson[x]) dfs2(lson[x],nowtop, len[nowtop]);
for(int i=h[x];~i;i=ne[i])
{
int y=e[i];
if(y!=fa[x]&&y!=lson[x]) dfs2(y,y, 0);
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; ++i) scanf("%d", &a[i]);
sort(a + 1, a + 1 + m);
reverse(a + 1, a + 1 + m);
memset(h, -1, sizeof h);
for(int i=2, x;i<=n;++i)
{
cin >> x;
x++;
add(i, x), add(x, i);
}
root = 1;
dfs1(root); // 第一次求dep, fa, maxlen, lson
dfs2(root,root, 0); // 第二次求top
set<int> s;
for (int i = 1; i <= n; ++i) s.insert(top[i]);
LL ans = 0;
int i = 0;
vector<int> v;
for (auto si: s) v.push_back(len[si]);
sort(v.begin(), v.end());
reverse(v.begin(), v.end());
for (int i = 0; i < m; ++i)
{
if (i == 0) v[i]--;
ans += a[i + 1] * (LL)(v[i]);
}
cout << ans << endl;
return 0;
}
2.2.2 查詢一個點的k級祖先
2.2.3 O(n)處理可合併的與深度有關的子樹資訊
3. 例題
3.1 重鏈剖分
P2590 [ZJOI2008]樹的統計
題意:
一棵樹上有 n 個節點,編號分別為 1 到 n,每個節點都有一個權值 w。
我們將以下面的形式來要求你對這棵樹完成一些操作:
I. CHANGE u t : 把結點 u 的權值改為 t。
II. QMAX u v: 詢問從點 u 到點 v 的路徑上的節點的最大權值。
III. QSUM u v: 詢問從點 u 到點 v 的路徑上的節點的權值和。
注意:從點 u 到點 v 的路徑上的節點包括 u 和 v 本身。
題解:
只需要線段樹加單點修改區間查詢的線段樹即可,模板題
程式碼:
#include<bits/stdc++.h>
using namespace std;
const int N = 3e4 + 10, INF = 1e9 + 10;
int tot, num;
int n, m, r;
// dat1:sum, dat2:max
int w[N], a[N], dat1[N * 4], dat2[N * 4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N * 2], ne[N * 2], idx; // 鄰接表陣列
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; // dep深度 dfn搜尋序 wson重兒子 size子樹大小 top鏈頭 fa父節點
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 得到sz, fa, dep, wson陣列
void dfs1(int u)
{
dep[u] = dep[fa[u]] + 1;
sz[u] = 1;
for(int i = h[u]; ~i; i = ne[i])
{
int j=e[i];
if(j == fa[u]) continue;
fa[j] = u;
dfs1(j);
sz[u] += sz[j];
if(sz[j] > sz[wson[u]]) wson[u] = j; // 這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
}
}
// 得到dfn, top陣列
void dfs2(int u, int nowtop)
{
dfn[u] = ++num;
w[num] = a[u];
//以搜尋序重排權值
top[u] = nowtop;
if(wson[u]) dfs2(wson[u], nowtop); // 先搜尋重兒子
for(int i = h[u]; ~i; i = ne[i]) // 然後搜尋輕兒子
{
int y = e[i];
if(y == fa[u] || y == wson[u]) continue;
dfs2(y, y);
}
}
void pushup(int rt) {
dat1[rt] = dat1[rt << 1] + dat1[rt << 1 | 1];
dat2[rt] = max(dat2[rt << 1], dat2[rt << 1 | 1]);
}
// 建線段樹,rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界
void build(int rt, int l, int r)
{
if(l == r)
{
dat1[rt] = dat2[rt] = w[l];
return ;
}
int mid = (l + r) >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid+1, r);
pushup(rt);
}
void modify(int rt, int l, int r, int x, int y) {
if (l == r) {
dat1[rt] = y;
dat2[rt] = y;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) modify(rt << 1, l, mid, x, y);
else modify(rt << 1 | 1, mid + 1, r, x, y);
pushup(rt);
}
int query1(int rt, int l, int r, int L, int R)
{
if(L <= l && r <= R) return dat1[rt];
int mid = (l + r)>>1;
int ans = 0;
if(L <= mid) ans += query1(rt << 1, l, mid, L, R);
if(mid < R) ans += query1(rt << 1 | 1, mid + 1, r, L, R);
return ans;
}
int query2(int rt, int l, int r, int L, int R) {
if (L <= l && r <= R) return dat2[rt];
int mid = (l + r) >> 1;
int ans = -INF;
if (L <= mid) ans = max(ans, query2(rt << 1, l, mid, L, R));
if (mid < R) ans = max(ans, query2(rt << 1 | 1, mid + 1, r, L, R));
return ans;
}
// 求樹從 x 到 y 結點最短路徑上所有節點的值之和
int path_query_Tree1(int x, int y)
{
//兩點間的修改
int ans = 0;
while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x點為深的那個點
ans += query1(1, 1, n, dfn[top[x]], dfn[x]);
x = fa[top[x]]; // x每次跳一條鏈
}
if(dep[x] > dep[y]) swap(x, y); // 讓x成為深度更淺的那個點
ans += query1(1, 1, n, dfn[x], dfn[y]);
return ans;
}
// 求樹從 x 到 y 結點最短路徑上所有節點的max
int path_query_Tree2(int x, int y)
{
//兩點間的修改
int ans = -INF;
while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x點為深的那個點
ans = max(ans, query2(1, 1, n, dfn[top[x]], dfn[x]));
x = fa[top[x]]; // x每次跳一條鏈
}
if(dep[x] > dep[y]) swap(x, y); // 讓x成為深度更淺的那個點
ans = max(ans, query2(1, 1, n, dfn[x], dfn[y]));
return ans;
}
void modify_Tree(int x, int y) {
modify(1, 1, n, dfn[x], y);
}
int main()
{
scanf("%d", &n); // 讀入點數、邊數、根、模數
r = 1;
// 讀入邊,建樹
memset(h, -1, sizeof h);
for(int i = 1, x, y; i < n; i++)
{
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
for(int i = 1; i <= n; i++) scanf("%d", &a[i]); // 讀入每個點的權值
scanf("%d", &m);
// 兩次dfs把樹按照重鏈剖分
dfs1(r); // 得到sz, fa, dep, wson陣列
dfs2(r, r); // 得到dfn, top陣列
build(1, 1, n);
// m次詢問
for(int i=1, u, v; i<=m; i++)
{
char op[10];
scanf("%s", op);
scanf("%d%d", &u, &v);
if (op[0] == 'Q' && op[1] == 'S') printf("%d\n", path_query_Tree1(u, v));
else if (op[0] == 'Q' && op[1] == 'M') printf("%d\n", path_query_Tree2(u, v));
else modify_Tree(u, v);
}
return 0;
}
P2146 [NOI2015]軟體包管理器
題意:
你決定設計你自己的軟體包管理器。不可避免地,你要解決軟體包之間的依賴問題。如果軟體包 a 依賴軟體包 b,那麼安裝軟體包 a 以前,必須先安裝軟體包 b。同時,如果想要解除安裝軟體包 b,則必須解除安裝軟體包 a。
現在你已經獲得了所有的軟體包之間的依賴關係。而且,由於你之前的工作,除 0 號軟體包以外,在你的管理器當中的軟體包都會依賴一個且僅一個軟體包,而 0 號軟體包不依賴任何一個軟體包。且依賴關係不存在環
現在你要為你的軟體包管理器寫一個依賴解決程式。根據反饋,使用者希望在安裝和解除安裝某個軟體包時,快速地知道這個操作實際上會改變多少個軟體包的安裝狀態(即安裝操作會安裝多少個未安裝的軟體包,或解除安裝操作會解除安裝多少個已安裝的軟體包),你的任務就是實現這個部分。
注意,安裝一個已安裝的軟體包,或解除安裝一個未安裝的軟體包,都不會改變任何軟體包的安裝狀態,即在此情況下,改變安裝狀態的軟體包數為 0。
題解: 通過分析可以知道軟體依賴關係是一棵樹,當安裝軟體時,就是影響從根(0節點)到x;當解除安裝軟體時,就是影響以x為根的子樹。所以只需要線段樹lazy維護區間染色,dat維護區間和即可。
程式碼:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int tot, num;
int n, m, r;
int w[N], a[N], dat[N * 4], lazy[N * 4]; // w[i]=j表示時間戳為i的點的值為j,a[]輸入每個節點的值,dat線段樹每個點權值,lazy線段樹每個點的懶標記
int h[N], e[N * 2], ne[N * 2], idx; // 鄰接表陣列
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; // dep深度 dfn搜尋序 wson重兒子 size子樹大小 top鏈頭 fa父節點
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 得到sz, fa, dep, wson陣列
void dfs1(int u)
{
dep[u] = dep[fa[u]]+1;
sz[u] = 1;
for(int i = h[u]; ~i; i = ne[i])
{
int j=e[i];
if(j == fa[u]) continue;
fa[j] = u;
dfs1(j);
sz[u] += sz[j];
if(sz[j] > sz[wson[u]]) wson[u] = j; // 這裡要注意根節點不能設為0,否則根節點的最重鏈無法更新,始終為0
}
}
// 得到dfn, top陣列
void dfs2(int u, int nowtop)
{
dfn[u] = ++num;
w[num] = a[u];
//以搜尋序重排權值
top[u] = nowtop;
if(wson[u]) dfs2(wson[u], nowtop); // 先搜尋重兒子
for(int i = h[u]; ~i; i = ne[i]) // 然後搜尋輕兒子
{
int y=e[i];
if(y ==fa[u]||y == wson[u]) continue;
dfs2(y, y);
}
}
void pushup(int rt) {
dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}
// 建線段樹,rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界
void build(int rt, int l, int r)
{
if(l==r)
{
dat[rt] = 0;
lazy[rt] = -1;
return ;
}
int mid=(l + r)>>1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid+1, r);
pushup(rt);
}
// 下傳
void pushdown(int rt, int l, int r)
{
if (lazy[rt] == -1) return;
int mid=(l + r)>>1;
dat[rt << 1] = lazy[rt]*(mid - l + 1), lazy[rt << 1] = lazy[rt];
dat[rt << 1 | 1] = lazy[rt]*(r-mid), lazy[rt << 1 | 1] = lazy[rt];
lazy[rt]=-1;
}
// rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界, l為需要修改的左區間,r為需要修改的右區間
void modify(int rt, int l, int r, int L, int R, int k)
{
if(L <= l && r <= R)
{
dat[rt] = k*(r-l+1);
lazy[rt] = k;
return ;
}
pushdown(rt, l, r);
int mid = (l + r)>>1;
if(L <= mid) modify(rt << 1, l, mid, L, R, k);
if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, k);
pushup(rt);
}
// rt為根,l為rt點管轄的左邊界, r為rt點管轄的有邊界, l為需要查詢的左區間,r為查詢的右區間
int query(int rt, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
return dat[rt];
}
pushdown(rt, l, r);
int mid = (l + r)>>1;
int ans = 0;
if(L <= mid) ans += query(rt << 1, l, mid, L, R);
if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R);
return ans;
}
// 求樹從 x 到 y 結點最短路徑上所有節點的值之和
int path_query_Tree(int x, int y)
{
//兩點間的修改
int ans = 0;
while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x點為深的那個點
ans += query(1, 1, n, dfn[top[x]], dfn[x]);
x = fa[top[x]]; // x每次跳一條鏈
}
if(dep[x] > dep[y]) swap(x, y); // 讓x成為深度更淺的那個點
ans += query(1, 1, n, dfn[x], dfn[y]);
return ans;
}
// 將樹從 x到 y 結點最短路徑上所有節點的值都賦值為 z
void path_modify_Tree(int x, int y, int k)
{
//樹上兩點距離
while(top[x] != top[y]) // 把x點和y點整到一條重鏈上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y); // 讓x成為對應的頭部深度更大的那個點
modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子樹和
x = fa[top[x]]; // x跳到原來x的頭部的父節點
}
if(dep[x] > dep[y]) swap(x, y); // 讓x成為深度更淺的那個點
modify(1, 1, n, dfn[x], dfn[y], k);
}
// 求以 x 為根節點的子樹內所有節點值之和
int Point_query_Tree(int rt)
{
//由搜尋序的特點可得,子樹的搜尋序一定比根大
return query(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1);
}
void Point_modify_Tree(int rt, int k)
{
modify(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1, k);
}
int main()
{
scanf("%d", &n);
r = 1;
// 讀入邊,建樹
memset(h, -1, sizeof h);
for (int i = 2, x; i <= n; ++i) {
scanf("%d", &x);
x++;
add(i, x), add(x, i);
}
scanf("%d", &m);
// 兩次dfs把樹按照重鏈剖分
dfs1(r); // 得到sz, fa, dep, wson陣列
dfs2(r, r); // 得到dfn, top陣列
build(1, 1, n);
// m次詢問
char op[10];
for(int i=1, x; i<=m; i++)
{
scanf("%s", op);
scanf("%d", &x);
x++;
if(op[0] == 'i')
{
int cnt1 = path_query_Tree(1, x);
path_modify_Tree(1, x, 1);
printf("%d\n", path_query_Tree(1, x) - cnt1);
}
else
{
int cnt1 = Point_query_Tree(x);
Point_modify_Tree(x, 0);
printf("%d\n", cnt1); //求樹從 x 到 y 結點最短路徑上所有節點的值之和
}
}
return 0;
}