\(\color{red}(-114514)\) P1424 小魚的航程(改進版)
- 有一隻小魚,它平日每天游泳 \(250\) 公里,週末休息(實行雙休日),假設從周 \(x\) 開始算起,過了 \(n\) 天以後,小魚一共累計游泳了多少公里呢?
太難了,先咕咕咕。
\(\color{red}(1)\) UOJ387 To Do Tree
-
有 \(n\) 個任務,做第 \(i\) 個任務需要先做第 \(f_i\) 個任務。依賴關係形成了一棵樹,樹根為任務 \(1\)。每天你可以完成 \(m\) 個任務,這 \(m\) 個任務之間不能有依賴關係。求最少的完成所有任務的天數。
-
\(n \le 10^5\)。
貪心策略是每次找子樹最大的任務做。
實現上維護一個堆,儲存當前哪些任務可以做但還沒做,按照子樹大小從大到小排序。每次取堆中前 \(m\) 大即可。
$\color{blue}\text{Code}$
struct Tree {
vector<int> g[N];
void add(int a, int b) { g[a].push_back(b); }
vector<int> operator [](const int &u) const { return g[u]; }
}T;
int n, m, fa[N], sz[N];
void Luogu_UID_748509() {
fin >> n >> m;
fill(sz + 1, sz + n + 1, 1);
for (int i = 2; i <= n; ++ i ) {
fin >> fa[i];
T.add(fa[i], i);
}
for (int i = n; i >= 2; -- i ) {
sz[fa[i]] += sz[i];
}
priority_queue<PII> q;
q.emplace(sz[1], 1);
int tmp = 0;
vector<vector<int> > ans;
while (tmp < n) {
vector<int> vec;
for (int i = 1; i <= m && q.size(); ++ i ) {
vec.emplace_back(q.top().second);
q.pop();
++ tmp;
}
ans.emplace_back(vec);
for (int u : vec) {
for (int v : T[u]) {
q.emplace(sz[v], v);
}
}
}
fout << ans.size() << '\n';
for (vector<int> t : ans) {
fout << t.size() << ' ' << t;
}
}
\(\color{red}(2)\) P4211 [LNOI2014] LCA
- 給定一顆 \(n\) 的節點的樹。\(m\) 次詢問 \(\sum_{i=l}^r \operatorname{depth}_{\operatorname{lca}(i, z)}\)。
- \(n, m \le 5 \times 10^4\)。
考慮幾個弱化版本:
- \(m\) 次詢問 \(\operatorname{depth}_{\operatorname{lca}(x, y)}\)。
顯然可以用樸素做法。這裡的做法是這樣的:
- 將 \(x\) 到根上每個點加 \(1\),那麼 \(y\) 到根的點權和即答案。原因是 \(x, y\) 到根的公共路徑長度就是它們最近公共祖先的深度。
- 實現用樹剖解決。
$\color{blue}\text{Code}$
while (m -- ) {
int x, y;
scanf("%d%d", &x, &y);
modify(1, x, 1); // 1 到 x 的路徑加一
printf("%d\n", query(1, y)); // 1 到 y 的路徑和
modify(1, x, -1); // 清空
}
- 單次詢問 \(\sum_{i=l}^r \operatorname{depth}_{\operatorname{lca}(i, z)}\)。
顯然也可以用樸素做法。這裡我們延續上一問的做法:
- 對於所有 \(i \in [l, r]\),將 \(i\) 到根上每個點累加 \(1\),那麼 \(z\) 到根的點權和即答案。
$\color{blue}\text{Code}$
int l, r, z;
scanf("%d%d%d", &l, &r, &z);
for (int i = l; i <= r; ++ i ) modify(1, i, 1); // 1 到 i 的路徑加一
printf("%d\n", query(1, z)); // 1 到 z 的路徑和
- \(m\) 次詢問 \(\sum_{i=\color{red}\mathbf1}^r \operatorname{depth}_{\operatorname{lca}(i, z)}\)。
顯然不能用樸素做法了。做法是這樣的:
- 考慮離線所有詢問。vector 以 \(r\) 做下標,儲存每個詢問的編號和 \(z\)。即
vec[r].push_back(make_pair(i, z))
。 - 列舉 \(i = (1, 2, \dots, n)\),並每次將 \(i\) 到根上每個點累加 \(1\)。
- 然後訪問 vector 的 \(i\) 中的所有元素 \((j, z)\),我們將 \(z\) 到根的點權和累加到詢問 \(j\) 的答案中。
$\color{blue}\text{Code}$
int res[N]; // 第 i 問的答案
vector<pair<int, int> > vec[N];
for (int i = 1; i <= m; ++ i ) {
scanf("%d%d", &a[i].r, &a[i].z);
vec[a[i].r].push_back({i, a[i].z});
}
for (int i = 1; i <= n; ++ i ) {
modify(1, i, 1); // 1 到 i 的路徑加一
for (pair<int, int> t : vec[i]) {
int a = t.first, b = t.second;
res[a] += query(1, b); // 1 到 i 的路徑和
}
}
for (int i = 1; i <= n; ++ i ) printf("%d\n", res[i]);
- \(m\) 次詢問 \(\sum_{i=l}^r \operatorname{depth}_{\operatorname{lca}(i, z)}\),即本題。
上一問差分即可。
$\color{blue}\text{Code}$
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 50010, M = N << 1;
int n, m, fa[N];
int h[N], e[M], ne[M], idx;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
vector<pair<int, int> > vec[N];
int res[N];
int dep[N], top[N], son[N], id[N], cnt, sz[N];
void dfs1(int u) {
dep[u] = dep[fa[u]] + 1;
sz[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
dfs1(v);
sz[u] += sz[v];
if (sz[u] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int t) {
top[u] = t;
id[u] = ++ cnt;
if (son[u]) {
dfs2(son[u], t);
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v != son[u]) dfs2(v, v);
}
}
}
struct Tree {
int l, r, v, tag;
}tr[N << 2];
void pushup(int u) {
tr[u].v = tr[u << 1].v + tr[u << 1 | 1].v;
}
void calc(int u, int k) {
tr[u].tag += k;
tr[u].v += k * (tr[u].r - tr[u].l + 1);
}
void pushdown(int u) {
calc(u << 1, tr[u].tag), calc(u << 1 | 1, tr[u].tag);
tr[u].tag = 0;
}
void build(int u, int l, int r) {
tr[u] = {l, r, 0, 0};
if (l != r) {
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
}
void modify(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) calc(u, 1);
else {
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (l <= mid) modify(u << 1, l, r);
if (r > mid) modify(u << 1 | 1, l, r);
pushup(u);
}
}
int query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
int res = 0, mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (l <= mid) res = query(u << 1, l, r);
if (r > mid) res += query(u << 1 | 1, l, r);
return res;
}
void Tree_modify(int a, int b) {
while (top[a] != top[b]) {
if (dep[top[a]] < dep[top[b]]) swap(a, b);
modify(1, id[top[a]], id[a]);
a = fa[top[a]];
}
if (dep[a] > dep[b]) swap(a, b);
modify(1, id[a], id[b]);
}
int Tree_query(int a, int b) {
int res = 0;
while (top[a] != top[b]) {
if (dep[top[a]] < dep[top[b]]) swap(a, b);
res += query(1, id[top[a]], id[a]);
a = fa[top[a]];
}
if (dep[a] > dep[b]) swap(a, b);
return res + query(1, id[a], id[b]);
}
signed main() {
memset(h, -1, sizeof h);
scanf("%lld%lld", &n, &m);
for (int i = 2; i <= n; ++ i ) {
scanf("%lld", fa + i);
fa[i] ++ ;
add(fa[i], i);
}
for (int i = 1; i <= m; ++ i ) {
int l, r, z;
scanf("%lld%lld%lld", &l, &r, &z);
vec[1 + r].push_back({i, 1 + z});
vec[l].push_back({-i, 1 + z});
}
dfs1(1), dfs2(1, 0);
build(1, 1, n);
for (int i = 1; i <= n; ++ i ) {
Tree_modify(1, i);
for (pair<int, int> t : vec[i]) {
int a = abs(t.first), b = t.second;
int k = t.first > 0 ? 1 : -1;
res[a] += k * Tree_query(1, b);
}
}
for (int i = 1; i <= m; ++ i ) printf("%lld\n", res[i] % 201314);
return 0;
}
\(\color{red}(3)\) P2680 [NOIP2015 提高組] 運輸計劃
- 給定一棵 \(n\) 個點的樹,邊有邊權 \(w_i\)。給定 \(m\) 條路徑 \((u_i,v_i)\)。你可以選擇一條邊,將其邊權變為 \(0\)。最小化這 \(m\) 條路徑長度的最大值。
- \(n, m \le 3 \times 10^5\)。
二分答案 \(mid\)。
對於原來路徑長度 \(\le mid\),我們無需考慮。換句話說,我們需要考慮的是長度 \(> mid\) 的路徑。
對於這些路徑而言,我們希望透過僅改變樹上一條邊,讓這些路徑的長度都變得 \(\le mid\)。顯然這條邊需要是這些路徑的交,而且是交中邊權最大的。
找路徑交可以用樹上差分的套路。
找到這條設為 \(0\) 的邊後簡單判斷一下即可。
$\color{blue}\text{Code}$
#include <bits/stdc++.h>
using namespace std;
const int N = 300010, M = N * 2, K = 19;
int n, m;
int h[N], e[M], ne[M], idx, w[M];
int fa[N][K], dep[N], dis[N];
int seq[N], cnt;
struct Path
{
int a, b, p, d;
}q[N];
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] =c, h[a] = idx ++ ;
}
void dfs(int u, int F, int D)
{
seq[cnt ++ ] = u;
dep[u] = D;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == F) continue;
fa[j][0] = u;
for (int k = 1; k < K; ++ k )
fa[j][k] = fa[fa[j][k - 1]][k - 1];
dis[j] = dis[u] + w[i];
dfs(j, u, D + 1);
}
}
int lca(int a, int b)
{
if (dep[a] < dep[b]) swap(a, b);
for (int k = K - 1; ~k; -- k )
if (dep[fa[a][k]] >= dep[b])
a = fa[a][k];
if (a == b) return a;
for (int k = K - 1; ~k; -- k )
if (fa[a][k] != fa[b][k])
a = fa[a][k], b = fa[b][k];
return fa[a][0];
}
int sum[N];
bool chk(int mid)
{
memset(sum, 0, sizeof sum);
int c = 0, mx = 0;
for (int i = 0; i < m; ++ i )
{
int a = q[i].a, b = q[i].b, p = q[i].p, d = q[i].d;
if (d > mid)
{
++ c;
mx = max(mx, d);
++ sum[a], ++ sum[b], sum[p] -= 2;
}
}
if (!c) return true;
for (int i = n - 1; ~i; -- i )
{
int j = seq[i];
sum[fa[j][0]] += sum[j];
}
for (int i = 1; i <= n; ++ i )
if (sum[i] == c && mx - dis[i] + dis[fa[i][0]] <= mid)
return true;
return false;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i < n; ++ i )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs(1, -1, 1);
for (int i = 0; i < m; ++ i )
{
int a, b;
cin >> a >> b;
int p = lca(a, b);
int d = dis[a] + dis[b] - dis[p] * 2;
q[i] = {a, b, p, d};
}
int l = 0, r = 3e8;
while (l < r)
{
int mid = l + r >> 1;
if (chk(mid)) r = mid;
else l = mid + 1;
}
cout << l;
return 0;
}
\(\color{red}(4)\) P2486 [SDOI2011] 染色 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
- 給定一棵 \(n\) 個點的樹,每個點上有一個顏色。你需要支援兩種操作:
- 將一條鏈 \((x,y)\) 上的點全部染成顏色 \(c\)。
- 詢問一條鏈 \((x,y)\) 上的點的顏色組成了幾個顏色段。
- \(n \le 10^5\)。
好題!
-
16:09:寫完,RE
-
2 min later:計算重兒子時
son[u] = sz[v]
,但輸出極大值 2088774347。 -
2 min later:線段樹初始化沒有用樹剖後的編號
id[l]
而是l
。 -
114514 min later:tmd 不做了。