学自:bztMinamoto
点分治是个很容易学的算法,就像拉格朗日插值法,生成函数一样,遇到这种题目不会就是不会,但是算法又很容易学,学起来性价比高,比较推荐大家也学学这些算法…
P3806 【模板】点分治1
点分治入门题
n^2写法
#include<bits/stdc++.h>
#define pi pair<int, int>
#define mk make_pair
using namespace std;
const int maxn = 1e4 + 5, N = 1e7 + 5, inf = 1e9;
int cnt[N], p[maxn], sz[maxn], d[maxn], vis[maxn], size, rt, mn, M;
vector<pi> G[maxn];
void getroot(int u, int fa) {
int cat = 0;
sz[u] = 1;
for (auto tmp : G[u])
if (tmp.first != fa && !vis[tmp.first]) {
int v = tmp.first;
getroot(v, u);
sz[u] += sz[v];
cat = max(cat, sz[v]);
}
cat = max(cat, size - sz[u]);
if (cat < mn)
mn = cat, rt = u;
}
void getdis(int u, int val, int fa) {
d[++M] = val;
for (auto tmp : G[u])
if (tmp.first != fa && !vis[tmp.first])
getdis(tmp.first, val + tmp.second, u);
}
void solve(int u, int val, int opt) {
M = 0;
getdis(u, val, 0);
for (int i = 1; i <= M; i++)
for (int j = 1; j < i; j++)
if (d[i] + d[j] < N)
cnt[d[i] + d[j]] += opt;
}
void dfs(int u, int fa) {
solve(u, 0, 1);
vis[u] = 1;
int tot = size;
for (auto tmp : G[u])
if (tmp.first != fa && !vis[tmp.first]) {
int v = tmp.first;
solve(v, tmp.second, -1);
mn = inf, rt = 0;
size = (sz[v] > sz[u]) ? tot - sz[u] : sz[v];
getroot(v, 0);
dfs(rt, 0);
}
}
int main() {
int n, q, u, v, w, x;
scanf("%d%d", &n, &q);
for (int i = 1; i < n; i++) {
scanf("%d%d%d", &u, &v, &w);
G[u].push_back(mk(v, w));
G[v].push_back(mk(u, w));
}
size = n;
mn = inf;
getroot(1, 0);
dfs(rt, 0);
while (q--) {
scanf("%d", &x);
if (cnt[x])
puts("AYE");
else
puts("NAY");
}
}
qnlogn写法:
#include<bits/stdc++.h>
#define pi pair<int, int>
#define mk make_pair
using namespace std;
const int maxn = 1e4 + 5, N = 1e7 + 5, inf = 1e9;
int cnt[N], p[maxn], sz[maxn], d[maxn], vis[maxn], size, rt, mn, M, qry[maxn], q, vis2[N];
vector<pi> G[maxn];
void getroot(int u, int fa) {
int cat = 0;
sz[u] = 1;
for (auto tmp : G[u])
if (tmp.first != fa && !vis[tmp.first]) {
int v = tmp.first;
getroot(v, u);
sz[u] += sz[v];
cat = max(cat, sz[v]);
}
cat = max(cat, size - sz[u]);
if (cat < mn)
mn = cat, rt = u;
}
void getdis(int u, int val, int fa) {
d[++M] = val;
for (auto tmp : G[u])
if (tmp.first != fa && !vis[tmp.first])
getdis(tmp.first, val + tmp.second, u);
}
void solve(int u, int val, int opt) {
M = 0;
getdis(u, val, 0);
for (int i = 1; i <= M; i++) {
for (int j = 1; j <= q; j++)
if (d[i] <= qry[j]) {
cnt[j] += opt * vis2[qry[j] - d[i]];
if (d[i] == qry[j])
cnt[j] += opt;
}
if (d[i] < N)
vis2[d[i]]++;
}
for (int i = 1; i <= M; i++)
if (d[i] < N)
vis2[d[i]] = 0;
}
void dfs(int u, int fa) {
solve(u, 0, 1);
vis[u] = 1;
int tot = size;
for (auto tmp : G[u])
if (tmp.first != fa && !vis[tmp.first]) {
int v = tmp.first;
solve(v, tmp.second, -1);
mn = inf, rt = 0;
size = (sz[v] > sz[u]) ? tot - sz[u] : sz[v];
getroot(v, 0);
dfs(rt, 0);
}
}
int main() {
int n, u, v, w, x;
scanf("%d%d", &n, &q);
for (int i = 1; i < n; i++) {
scanf("%d%d%d", &u, &v, &w);
G[u].push_back(mk(v, w));
G[v].push_back(mk(u, w));
}
for (int i = 1; i <= q; i++)
scanf("%d", &qry[i]);
size = n;
mn = inf;
getroot(1, 0);
dfs(rt, 0);
for (int i = 1; i <= q; i++) {
if (cnt[i])
puts("AYE");
else
puts("NAY");
}
}
有网友对这题n^2写法复杂度不太理解,我简单证明一下:
复杂度证明:每个子树计算次数:
s
i
z
e
∗
s
i
z
e
/
2
size * size / 2
size∗size/2
所有重心为根的子树最坏总计算次数: 1 2 ( n 2 + n 2 4 ∗ 2 + n 2 16 ∗ 4 + n 2 64 ∗ 8 + . . . . ) \frac{1}{2}(n^2+\frac{n^2}{4}*2+\frac{n^2}{16}*4+\frac{n^2}{64}*8+....) 21(n2+4n2∗2+16n2∗4+64n2∗8+....)
我们知道重心树一共有log层,公式化简: 1 2 n 2 ( 1 − 1 2 l o g ) 1 − 1 2 = n 2 \frac{1}{2}\frac{{}n^{2}(1-\frac{1}{2^{log}})}{1-\frac{1}{2}}=n^{2} 211−21n2(1−2log1)=n2