树上问题
差分,LCA
运输计划
比较简单的题,9.13一遍过
首先比较容易想到二分,那么如何check呢,把所有大于mid的运输计划拎出来
这些之中应该找到他们交集中最大的一条,如果将他变成虫洞可以那就ok
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i ++ )
#define repf(i, a, b) for(int i = (a); i >= (b); i -- )
#define ls t[cur].lson
#define rs t[cur].rson
typedef unsigned long long ll;
using namespace std;
const int N = 3e5 + 10;
int n, m;
int head[N], nxt[N << 1], to[N << 1], w[N << 1], E;
int LCA[N], dis[N], dep[N], f[N][19];
struct node
{
int x, y, d, lca;
}cm[N];
inline void add(int x, int y, int z)
{
to[E] = y;
nxt[E] = head[x];
w[E] = z;
head[x] = E ++;
}
void dfs(int u, int fa)
{
for(int i = head[u]; ~i; i = nxt[i])
{
int x = to[i];
if(x == fa) continue;
dep[x] = dep[u] + 1, f[x][0] = u;
rep(i, 1, 18) f[x][i] = f[f[x][i-1]][i-1];
dis[x] = dis[u] + w[i];
dfs(x, u);
}
}
inline int lca(int x, int y)
{
if(dep[x] < dep[y]) swap(x, y);
int d = dep[x] - dep[y];
repf(i, 18, 0) if( d & (1 << i) ) x = f[x][i];
if(x == y) return x;
repf(i, 18, 0) if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
inline bool cmp(node n1, node n2) {return n1.d < n2.d;}
int c[N];
void ddd(int u, int fa)
{
for(int i = head[u]; ~i; i = nxt[i])
{
int x = to[i];
if(x == fa) continue;
ddd(x, u);
c[u] += c[x];
}
}
inline bool check(int x)
{
int l = 1, r = m, best = -1;
while(l <= r)
{
int mid = l + r >> 1;
if(cm[mid].d > x) r = mid - 1, best = mid;
else l = mid + 1;
}
if(best == -1) return 1;
// best ~ m
memset(c, 0, sizeof c);
int M = 0;
rep(i, best, m) c[cm[i].x] ++, c[cm[i].y] ++, c[cm[i].lca] -= 2, M = max(M, cm[i].d);
ddd(1, -1);
int MM = 0;
rep(i, 1, n) if(c[i] == m - best + 1) MM = max(MM, dis[i] - dis[f[i][0]]);
return M - MM <= x;
}
int main()
{
ios :: sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
memset(head, -1, sizeof head);
cin >> n >> m;
rep(i, 1, n - 1)
{
int x, y, z; cin >> x >> y >> z;
add(x, y, z); add(y, x, z);
}
dep[1] = 1; dfs(1, -1);
rep(i, 1, m)
{
cin >> cm[i].x >> cm[i].y;
cm[i].lca = lca(cm[i].x, cm[i].y);
cm[i].d = dis[cm[i].x] + dis[cm[i].y] - 2 * dis[cm[i].lca];
}
sort(cm + 1, cm + 1 + m, cmp);
int l = 0, r = cm[m].d, best = -1;
while(l <= r)
{
int mid = l + r >> 1;
if(check(mid)) r = mid - 1, best = mid;
else l = mid + 1;
}
cout << best;
return 0;
}
货车运输
首先小的边是能不走就不走的,所以先求出最大生成树
剩下就是求倍增求lca时顺便求出最边即可
[USACO10HOL] Cow Politics G
类比dfs求直径,先任取一个点x,求出距离x最远的点a,则a一定是直径的端点那么再求距离a的最远点即可
直径还有一些性质比如
一个点集的直径是x, y 现在加入一个点那么新的直径的一个端点必然是x或y。
合并两个点集,就是合并前的x->y, a->b(两条直径)这四个点的组合中必然有一条是直径
DFS序
记录dfs到每个点的第一时刻,称作这个点的dfs序。
好处: 子树 -> 区间
void dfs(int x)
{
tot ++; l[x] = tot;
for( x 的所有儿子 y ) dfs( y )
r[x] = tot;
}
l[x] ~ r[x] 就是x子树对应的区间
一下问题都是给定一棵树
问题1
单点修改,查询子树和
直接转化成区间上的单点修改,求区间和
问题2
单点修改,查询x->y的路径和
可以考虑差分
x -> y的路径和 = x到根 + y到根 - 2 * lca到根
那么考虑维护序列B b[i]表示i到根的和
每次单点修改i,收到影响的b[j]只有i子树内的点, 所以转化为区间加 (类比前缀和)
问题3
x -> y路径加, 查询单点值
还是考虑差分, 那么此时路径加变为b[x]+1, b[y]+1, b[lca]-1, b[fa[lca]]-1
那么影响一个点的就是他子树内的点,类比差分前缀和
问题4
子树加, 子树全部修改为一个值, 查询子树和
区间加, 区间覆盖, 区间和
问题5
给一颗子树 第一层加k,第二层减k 第三层加k 第四层减k........ 查询子树和
容易发现加的点层数的奇偶性是固定的,所以我们根据层的奇偶性,将他们放在两个区间内分别维护
DFS 序 1:单点修改 + 子树查询
DFS 序 2:子树修改 + 单点查询,子树修改 + 子树查询
DFS 序 3:树链修改 + 单点查询,树链修改 + 子树查询
DFS 序 4:单点修改 + 树链查询,子树修改 + 树链查询
(必须要用树链剖分来做):树链修改 + 树链查询
问题6
单点修改,子树修改,点到根路径查询
首先还是考虑维护b[i]表示i到根路径的和
那么单点修改就会影响整个x子树内的信息 所以子树加就行了(区间加)
那么子树加呢?
一眼看上去貌似不可做 其实就是以x为根的子树 第一层加val 第二层加2 * val 第三层加3 * val
转换一下就是加 (dep[y] - dep[x] + 1) 倍的val 参数分离一下 (1 - dep[x]) * val + dep[y] * val
前面的一项就是子树每个数加上即可,后面的单独维护一下每个点val的和,输出时 +smval * dep[i]即可
情报传递 link
首先看到问题,如果每次把每个点的点权加1,那实在太慢了。(小转化目前是T时刻,那么超过C的标记应该小于T - C,我们标记这个点开始计时的时刻)
考虑离线, 那么可以按照时间处理询问, 对于在T时刻查询大于C的个数等价于在 T - C 时刻查询大于0的个数
所以按照时间 T - C 处理每个询问
同时按照时间将点加进来
维护b[i] 表示 i -> 根的和
修改单点变成修改子树
天天爱跑步 link

首先我们只看 s -> lca 的这条路径,暴力的去走肯定是不行的,考虑打标记,在s上挂上一个从0时刻开始的点, 表示一个点在0时刻从s开始向根走。
为什么叫挂上?因为一个点可以是多条路径的起点
这样做我们如何统计一个点x有多少路径会被他看到呢,首先显然只有他子树内的点才有可能穿过他,那么在w[x]时刻刚刚好到达x代表
dep[y] - dep[x] = w[x] - t[y] (y开始走的时刻) 等价于 dep[y] + t[y] == w[x] + dep[x] = X
我们发现不就是数一下x子树内有多少个y满足dep[y] + t[y] = X吗
那么该怎么做呢?首先x的子树可以转化为一个区间,那么相当于求一个区间内等于X的数的个数
考虑差分[l, r]内X的个数可以转化为[1, r]的个数减去[1, l - 1]的个数, 这个东西离线下来用个桶扫描一遍即可
还差了一些事情,我们只将s挂了一个+1的start time=0的点,那么他到根路径都会被算贡献,我们应该减去lca以上的贡献,因为s -> lca并非到根。
所以还要再挂一个点在fa[lca]处抵消贡献,那么这个点的信息应该是什么呢?应该是时刻(dep[lca] - dep[s] + 1)开始的向根走的点 并且贡献是 -1。
这样一来所有在lca之上的点如果统计到了s的贡献1那么也会统计到FA:lca处的贡献-1。这样就完美抵消了。
之前只讨论了s -> lca这条路径,现在也得考虑下lca -> t的这条路径了。
我们在树上一般不会从上向下维护信息,因为一个爸爸有多个儿子,但是一个儿子只有一个父亲。
所以换种思路,我们在t处挂一个(tim, val)表示他是tim时刻到达t的路径,并且贡献是val(1/-1)。此时如何统计一个点x的答案呢?(只考虑lca -> t的贡献)
显然是要满足dep[t] - dep[x] = tim[t] - w[x]也就是dep[t] - tim[t] = dep[x] - w[x]
那么类似的数数就行了,还需要类似差分一样的抵消贡献。
小细节就是lca这个点不要把s, t的贡献算重了,我的处理方法就是一个在fa:lca打抵消标记,一个在lca处打
还可以特判。
代码我觉得挺难写,但是竟然一遍过了???
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i ++ )
#define repf(i, a, b) for(int i = (a); i >= (b); i -- )
typedef long long ll;
#define ls cur << 1
#define rs cur << 1 | 1
using namespace std;
const int N = 3e5 + 10;
const int M = 3e5 + 10;
const int BASE = 3e5;
int head[N], to[M << 1], nxt[N << 1], E;
inline void add(int x, int y)
{
to[E] = y;
nxt[E] = head[x];
head[x] = E ++;
}
int w[N], dep[N], f[N][19]; // 2&18
int l[N], r[N], cnt, fun[N];
void dfs(int u, int fa)
{
cnt ++; l[u] = cnt;
fun[cnt] = u;
for(int i = head[u]; ~i; i = nxt[i])
{
int x = to[i];
if(x == fa) continue;
dep[x] = dep[u] + 1;
f[x][0] = u;
rep(i, 1, 18) f[x][i] = f[f[x][i - 1]][i - 1];
dfs(x, u);
}
r[u] = cnt;
}
inline int lca(int x, int y)
{
if(dep[x] < dep[y]) swap(x, y);
int d = dep[x] - dep[y];
repf(i, 18, 0) if(d & (1 << i)) x = f[x][i];
if(x == y) return x;
repf(i, 18, 0) if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
struct KKK
{
int t, val;
};
vector<KKK> Z[N];
vector<KKK> F[N];
struct node
{
int r, sm, id, val;
}A[N << 1], B[N << 1];
int T[1000005], ans[N], TT[1000005];
bool cmp(node n1, node n2)
{
return n1.r < n2.r;
}
int main()
{
ios :: sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
memset(head, -1, sizeof head);
int n, m; cin >> n >> m;
rep(i, 1, n - 1)
{
int x, y; cin >> x >> y;
add(x, y); add(y, x);
}
dep[1] = 1;
dfs(1, -1);
rep(i, 1, n) cin >> w[i];
rep(i, 1, m)
{
int s, t; cin >> s >> t;
int lc = lca(s, t);
int x = dep[s] - dep[lc];
Z[l[s]].push_back({0, 1});
Z[l[f[lc][0]]].push_back({x + 1, -1});
F[l[t]].push_back({x + dep[t] - dep[lc], 1});
F[l[lc]].push_back({x, -1});
}
int tot1 = 0, tot2 = 0;
rep(i, 1, n)
{
// Z : w[x] + dep[x]
// F : dep[x] - w[x]
int x = l[i], y = r[i];
A[++ tot1] = {y, w[i] + dep[i], i, 1};
A[++ tot1] = {x - 1, w[i] + dep[i], i, -1};
B[++ tot2] = {y, - w[i] + dep[i] + BASE, i, 1};
B[++ tot2] = {x - 1, - w[i] + dep[i] + BASE, i, -1};
}
sort(A + 1, A + 1 + tot1, cmp);
sort(B + 1, B + 1 + tot2, cmp);
int p = 1;
rep(i, 1, tot1)
{
while(p <= A[i].r)
{
for(auto x : Z[p]) T[x.t + dep[fun[p]]] += x.val;
p ++;
}
ans[A[i].id] += A[i].val * T[A[i].sm];
}
p = 1;
rep(i, 1, tot2)
{
while(p <= B[i].r)
{
for(auto x : F[p]) TT[-x.t + dep[fun[p]] + BASE] += x.val;
p ++;
}
ans[B[i].id] += B[i].val * TT[B[i].sm];
}
rep(i, 1, n) cout << ans[i] << ' ';
return 0;
}

浙公网安备 33010602011771号