点分治
题目类型
告诉我们一棵无根树,求解树上距离关系等 ( ( (如有多少条长度不超过K的路径 ) ) ),如果直接求解可能时间复杂度会很大,我们可以通过换的重心的方式来实现降低时间复杂度。
思路
我们可以通过更换重心的方法,对不同重心进行寻找与重心相连的子树内的所有路径,分别求解相关内容即可,因为通过更换重心,我们每次可以将子树个数变化为 ≤ s i z e / 2 \le size/2 ≤size/2,其中 s i z e size size为子树的大小,这样我们只要便利 l o g 2 n log_2n log2n个子树即可,这样时间复杂度可以降到 n l o g 2 n nlog_2n nlog2n。
实现
洛谷3806
一道很经典的点分治题目
每次寻找重心,将树划分成
l
o
g
2
n
log_2n
log2n段,然后我们用一个数组存一下距离,然后求出每个子树到重心的所有距离,然后分三种情况讨论
1.
1.
1.两个点在同一个子树内 – 递归求解
2.
2.
2.两个点其中一个点是重心 – 判断两点距离是否为
m
m
m
3.
3.
3.两个点在不同子树内 – 存一个数组记录距离,然后查找
m
−
m-
m−一个点到重心的距离,再判断这个距离再其他子树是否存在
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10100, M = N * 2, S = 10010000;
int n, m, Q;
int h[N], ne[M], w[M], e[M], idx;
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
bool fl;
bool st[N];
int f[S], q[N], p[N];
int get_size(int u, int fa){
if(st[u]) return 0;
int res = 1;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue;
res += get_size(j, u);
}
return res;
}
int get_wc(int u, int fa, int tot, int &wc){
if(st[u]) return 0;
int sum = 1, ms = 0;
for(int i = h[u]; ~ i; i = ne[i]){
int j = e[i];
if(j == fa) continue;
int t = get_wc(j, u, tot, wc);
ms = max(ms, t);
sum += t;
}
ms = max(ms, tot - sum);
if(ms <= tot / 2) wc = u;
return sum;
}
void get_dist(int u, int fa, int dist, int &qt){
if(st[u] || dist > m) return;
q[qt ++] = dist;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue;
get_dist(j, u, dist + w[i], qt);
}
}
void calc(int u){
if(st[u] || fl) return;
get_wc(u, -1, get_size(u, -1), u);
int pt = 0;
st[u] = 1;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i], qt = 0;
get_dist(j, u, w[i], qt);
for(int k = 0; k < qt; k ++){
if(q[k] == m){
fl = 1;
for(int t = 0; t < pt; t ++) f[p[t]] = 0;
return;
}
if(f[m - q[k]]){
fl = 1;
for(int t = 0; t < pt; t ++) f[p[t]] = 0;
return;
}
p[pt ++] = q[k];
}
for(int k = 0; k < qt; k ++) f[q[k]] ++;
}
for(int i = 0; i < pt; i ++) f[p[i]] = 0;
for(int i = h[u]; ~i; i = ne[i]) calc(e[i]);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> Q;
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
while(Q --){
cin >> m;
fl = 0;
memset(st, 0, sizeof st);
calc(1);
if(fl) cout << "AYE\n";
else cout << "NAY\n";
}
return 0;
}
点分树
题目类型
告诉我们一棵无根树,求解树上距离关系等 ( ( (如有多少条长度不超过K的路径 ) ) ),有多组询问,询问次数很多。
思路
与点分治思路类似,但是因为多次查询,我们不能每次都去点分治。我们会发现一棵树的重心基本不会改变,这样我们就可以先预处理出来所有的重心,然后保留重心和父亲以及儿子之间的关系,就可以直接查找,效率会变高
实现
洛谷3241
这个就需要我们预处理重心,然后直接查找重心父亲和儿子即可,因为每个点度数很小,所以我们可以用
v
e
c
t
o
r
vector
vector将这些内容存下来,求解然后对于所有距离和,答案为所有满足条件的点p到u的距离+p到u的距离×g的不包含u的子联通块siz和+g的不包含u的子联通块中的点到u的距离之和,我们利用前缀和+二分的技巧,可以快速求出满足的个数,求解即可
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 150100, M = N * 2;
int n, Q, A;
int age[N];
int h[N], ne[M], e[M], idx, w[M];
bool st[N];
struct Father{
int u, num;
LL dist;
};
vector<Father> f[N];
struct Son{
int age;
LL dist;
bool operator< (const Son &W)const{
return age < W.age;
}
};
vector<Son> son[N][3];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int get_size(int u, int fa){
if(st[u]) return 0;
int res = 1;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue;
res += get_size(j, u);
}
return res;
}
int get_wc(int u, int fa, int tot, int &wc){
if(st[u]) return 0;
int sum = 1, ms = 0;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue;
int t = get_wc(j, u, tot, wc);
sum += t;
ms = max(ms, t);
}
ms = max(ms, tot - sum);
if(ms <= tot / 2) wc = u;
return sum;
}
void get_dist(int u, int fa, int dist, int wc, int k, vector<Son> &p){
if(st[u]) return;
f[u].push_back({wc, k, dist});
p.push_back({age[u], dist});
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(j == fa) continue;
get_dist(j, u, dist + w[i], wc, k, p);
}
}
void calc(int u){
if(st[u]) return;
get_wc(u, -1, get_size(u, -1), u);
st[u] = 1;
for(int i = h[u], k = 0; ~i; i = ne[i]){
int j = e[i];
if(st[j]) continue;
auto &p = son[u][k];
get_dist(j, -1, w[i], u, k, p);
k ++;
p.push_back({-1, 0}), p.push_back({A+1, 0});
sort(p.begin(), p.end());
for(int t = 0; t < p.size(); t ++) p[t].dist += p[t - 1].dist;
}
for(int i = h[u]; ~ i; i = ne[i]) calc(e[i]);
}
LL query(int u, int l, int r){
LL res = 0;
for(auto &t:f[u]){
int g = age[t.u];
if(g >= l && g <= r) res += t.dist;
for(int i = 0; i < 3; i ++){
if(i == t.num) continue;
auto &p = son[t.u][i];
if(p.empty()) continue;
int a = lower_bound(p.begin(), p.end(), Son({l, -1})) - p.begin();
int b = lower_bound(p.begin(), p.end(), Son({r+1, -1})) - p.begin();
res += t.dist * (b - a) + p[b - 1].dist - p[a - 1].dist;
}
}
for(int i = 0; i < 3; i ++){
auto &p = son[u][i];
if(p.empty()) continue;
int a = lower_bound(p.begin(), p.end(), Son({l, -1})) - p.begin();
int b = lower_bound(p.begin(), p.end(), Son({r+1, -1})) - p.begin();
res += p[b-1].dist - p[a-1].dist;
}
return res;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> Q >> A;
for(int i = 1; i <= n; i ++) cin >> age[i];
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
calc(1);
LL ans = 0;
while(Q --){
int u, a, b;
cin >> u >> a >> b;
int l = (ans + a) % A, r = (ans + b) % A;
if(l > r) swap(l, r);
ans = query(u, l, r);
cout << ans << "\n";
}
return 0;
}