点分治
点分治有两种统计答案的方案
方案1:分别处理子树
在处理完一颗子树的后, 再将节点统一加进备选答案中, 可以避免同颗子树上的两点相互干扰
洛谷P3806 【模板】点分治1
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
const int LIMIT = 1e8 + 10;
const int inf = 0x3f3f3f3f;
int m;
bitset<LIMIT> exist;
int head[N], ver[N << 1], cost[N << 1], nxt[N << 1], tot;
int cnt, temp[N];//temp表示该子树的状态, 在下一子树时会更新进exist
void add(int u, int v, int w){
ver[++tot] = v;
cost[tot] = w;
nxt[tot] = head[u];
head[u] = tot;
}
int ques[N], ans[N];
int del[N], sz[N], d[N];
int tmp, st, root;
void get_gravity(int n, int u, int fa){
sz[u] = 1;
int max_part = 0;
for(int i = head[u]; i; i = nxt[i]){
int v = ver[i];
if(v == fa || del[v]) continue;
get_gravity(n, v, u);
sz[u] += sz[v];
max_part = max(max_part, sz[v]);
}
max_part = max(max_part, n - sz[u]);
if(max_part < tmp){
tmp = max_part;
root = u;
}
}
inline void getAns(int x){
for(int i = 1; i <= m; i++) {
int k = ques[i];
if(k - x >= 0 && exist[k - x]) ans[i] = 1;
}
}
void getDis(int u, int fa){
for(int i = head[u]; i; i = nxt[i]){
int v = ver[i], w = cost[i];
if(v == fa || del[v]) continue;
d[v] = d[u] + w;
getAns(d[v]);
temp[++cnt] = d[v];
getDis(v, u);
}
}
void calc(int u) {
cnt = 0, st = 0;
d[u] = 0;
exist[d[u]] = 1;//exist[0] = 1
for(int i = head[u]; i; i = nxt[i]){
int v = ver[i], w = cost[i];
if(del[v]) continue;
d[v] = d[u] + w;
getAns(d[v]);
temp[++cnt] = d[v];
//分别处理每颗子树
getDis(v, u);
for(int j = st; j <= cnt; j++) exist[temp[j]] = 1;
st = cnt + 1;
}
// 分治完后, 还原数组
for(int i = 1; i <= cnt; i++) exist[temp[i]] = 0;
}
void vertex_partition(int u, int n){//点分治主要函数
tmp = inf;
get_gravity(n, u, 0);//重心
u = root;
calc(u);
del[root] = 1;
for(int i = head[root]; i; i = nxt[i]){
int v = ver[i];
if(del[v]) continue;
vertex_partition(v, sz[v]);
}
}
void solve(){
int n;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n - 1; i++){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
add(v, u, w);
}
for(int i = 1; i <= m; i++){
int k;
scanf("%d", &k);
ques[i] = k;
}
vertex_partition(1, n);
for(int i = 1; i <= m; i++){
if(ans[i]) printf("AYE\n");
else printf("NAY\n");
}
}
int main() {
int t = 1;
while(t--)solve();
return 0;
}
用vector开树状数组, 可以实现类似 Σ s i z e = n \Sigma size = n Σsize=n的操作, 其中 s i z e size size为同一层的各个点树状数组的大小
定义:点分治子树:点分治的每一层, 选取了重心后, 该重心的任一棵子树
应用 :指针扫描数组的方法中, 对于点分治过程中的某一层,用 r o o t root root表示重心,对于经过 r o o t root root的任意两个点 u , v u, v u,v我们有公式 f ( u , v ) f(u, v) f(u,v)[ u , v u, v u,v不属于同一棵子树] = f ( u , v ) − f ( u , v ) [ u , v = f(u, v)- f(u, v)[u, v =f(u,v)−f(u,v)[u,v属于同一颗子树 ] ] ] ,其中 f ( u , v ) [ u , v f(u, v)[u, v f(u,v)[u,v属于用一颗子树 ] ] ]即要求我们统计一棵点分治子树的贡献
定理:n个节点的树, 点分治子树总数等于n - 1.
证明:我们可以把每个点分治子树和点分治子树的重心建立一个双射,则除了第一个点, 其余点都与一个点分治子树一一对应。
定理应用:
HDU4918中, 我们要对每个节点建立树状数组, 运用该定理, 我们知道树状数组的总个数为2N。 (区别于树状数组的总长度2NlogN)
给一棵树, 实现两种操作
1:修改一个节点的权值
2:询问, 到u点距离不超过d的所有节点, 权值和是多少
#include<bits/stdc++.h>
#define lowbit(x) ((x) & (-x))
using namespace std;
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f;
struct GraphEdge {
int v, nxt;
};
struct NodeEdge {//链式前向星用
int root_idx, son_of_root_idx, nxt, dis_to_root;
};
struct BinaryIndexedTree {//树状数组
int n;
vector<int> tree;
void init(int len) {
n = len;
tree = vector<int>(len + 1);
}
void add(int p, int x) {
for (int i = p; i <= n; i += lowbit(i))
tree[i] += x;
}
int ask(int p) {
int ans = 0;
if (p > n) p = n;
for (int i = p; i > 0; i -= lowbit(i))
ans += tree[i];
return ans;
}
};
int w[N], del[N];
int Ghead[N], Gtot, Nhead[N], Ntot;
GraphEdge Gedge[N << 1];
NodeEdge Nedge[N << 5];
BinaryIndexedTree tree[N << 1];
int ncnt;
void add_graph(int u, int v) {
Gedge[++Gtot] = { v, Ghead[u] };
Ghead[u] = Gtot;
}
void add_node(int u, int root, int son_of_root, int dis) {
Nedge[++Ntot] = { root, son_of_root, Nhead[u], dis };
Nhead[u] = Ntot;
}
int root, temp;
int sz[N], dis[N];
void get_gravity(int u, int fa, int n) {
sz[u] = 1;
int max_part = 0;
for (int i = Ghead[u]; i; i = Gedge[i].nxt) {
int v = Gedge[i].v;
if (v == fa || del[v])continue;
get_gravity(v, u, n);
sz[u] += sz[v];
max_part = max(max_part, sz[v]);
}
max_part = max(max_part, n - sz[u]);
if (max_part < temp) {
temp = max_part;
root = u;
}
}
void get_size(int u, int fa) {//提根后树型改变, 需要重新更新size
sz[u] = 1;
for (int i = Ghead[u]; i; i = Gedge[i].nxt) {
int v = Gedge[i].v;
if (v == fa || del[v])continue;
get_size(v, u);
sz[u] += sz[v];
}
}
void dfs(int u, int fa, const int root_idx, const int subroot_idx) {
for (int i = Ghead[u]; i; i = Gedge[i].nxt) {
int v = Gedge[i].v;
if (v == fa || del[v]) continue;
dis[v] = dis[u] + 1;
tree[root_idx].add(dis[v] + 1, w[v]);
tree[subroot_idx].add(dis[v] + 1, w[v]);
add_node(v, root_idx, ncnt, dis[v]);
dfs(v, u, root_idx, subroot_idx);
}
}
void calc(int u) {
tree[++ncnt].init(sz[u] + 1);
int root_idx = ncnt;
dis[u] = 0;
tree[root_idx].add(dis[u] + 1, w[u]);//树状数组只能统计所有1-n的区间, 故对所有值加1的偏移量
add_node(u, root_idx, 0, dis[u]); // 小于等于d的所有点也包括自己
for (int i = Ghead[u]; i; i = Gedge[i].nxt) {
int v = Gedge[i].v;
if (del[v]) continue;
tree[++ncnt].init(sz[v] + 1);//Sigma (sz[v]) = sz[u], 保证这点才不会炸内存, +1与树状数组的平移有关
dis[v] = dis[u] + 1;
tree[root_idx].add(dis[v] + 1, w[v]);
tree[ncnt].add(dis[v] + 1, w[v]);
add_node(v, root_idx, ncnt, dis[v]);
dfs(v, u, root_idx, ncnt);
}
}
void vertex_divide(int n, int u) {
temp = inf;
get_gravity(u, 0, n);
u = root;
get_size(u, 0);
calc(u);
del[u] = 1;
for (int i = Ghead[u]; i; i = Gedge[i].nxt) {
int v = Gedge[i].v;
if (del[v]) continue;
vertex_divide(sz[v], v);
}
}
void init(int n) {
Ntot = Gtot = ncnt = 0;
memset(Ghead, 0, 4 * (n + 1));
memset(Nhead, 0, 4 * (n + 1));
memset(del, 0, 4 * (n + 1));
}
void solve(int n, int q) {
init(n);
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
for (int i = 1; i <= n - 1; i++) {
int u, v;
scanf("%d%d", &u, &v);
add_graph(u, v);
add_graph(v, u);
}
vertex_divide(n, 1);
while (q--) {
char op[5];
int u, x;
scanf("%s%d%d", op, &u, &x);
if (op[0] == '?') {
int ans1 = 0, ans2 = 0;
for (int i = Nhead[u]; i; i = Nedge[i].nxt) {
int root_idx = Nedge[i].root_idx;
int son_of_root_idx = Nedge[i].son_of_root_idx;
int dis = Nedge[i].dis_to_root;
ans1 += tree[root_idx].ask(x - dis + 1);
ans2 += tree[son_of_root_idx].ask(x - dis + 1);
}
printf("%d\n", ans1 - ans2);
}
else {
for (int i = Nhead[u]; i; i = Nedge[i].nxt) {
int root_idx = Nedge[i].root_idx;
int son_of_root_idx = Nedge[i].son_of_root_idx;
int dis = Nedge[i].dis_to_root;
tree[root_idx].add(dis + 1, x - w[u]);
tree[son_of_root_idx].add(dis + 1, x - w[u]);
}
w[u] = x;
}
}
}
int main() {
int n, q;
while (~scanf("%d%d", &n, &q)) solve(n, q);
return 0;
}
检查点分治的时间复杂度:
蓝书上曾有一句话, 在点分治类的做法中, 时间复杂度很容易假, 而对于一些假的复杂度,菊花套链很容易卡掉。
以Prime Distance On Tree 为例
题意:简化后,是给一棵边权为1的树, 问树上两点间距离为质数的点有几对
做法:点分治+fft
在进行fft时, 我们发现fft的次数最高可以高达n次(构造一个菊花图即可,相对的, fft数组的长度会很小)那么, 我们该如何计算该算法的时间复杂度呢
设
S
S
S为当前正在处理的树,
S
1
,
S
2
,
.
.
.
S_1, S_2, ...
S1,S2,...为它的子树, 若当前正在处理第
i
i
i棵子树, 则进行fft的两个数组, 长度分别为
m
a
x
(
d
[
S
1
]
,
d
[
S
2
]
,
.
.
.
d
[
S
i
−
1
]
)
max(d[S_1], d[S_2], ... d[S_{i-1}])
max(d[S1],d[S2],...d[Si−1]) 和
d
[
S
i
]
d[S_i]
d[Si],(其中
d
[
S
i
]
d[S_i]
d[Si]表示
S
i
S_i
Si这课子树中最深的节点的深度),也就是说, 若
d
[
S
1
]
d[S_1]
d[S1]特别大(如
d
[
S
1
]
=
n
2
d[S_1] = \frac{n}{2}
d[S1]=2n), 则接下来的每一次fft都需要
O
(
n
2
l
o
g
n
2
)
O(\frac{n}{2}log\frac{n}{2})
O(2nlog2n)的时间, 结合我们前面所说的fft最多可进行n次, 忽略常数后, 时间复杂度高达
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn), 这个上界不能让我们满意。 那么该怎么做才能确保时间复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)呢
我们考虑对子树按照
d
d
d排序,则有
m
a
x
(
d
[
S
1
]
,
d
[
S
2
]
,
.
.
.
,
d
[
S
i
]
)
=
d
[
S
i
]
max(d[S_1], d[S_2], ... ,d[S_i]) = d[S_i]
max(d[S1],d[S2],...,d[Si])=d[Si]这样进行一次fft的时间复杂度为
O
(
d
[
S
i
−
1
]
+
d
[
S
i
]
l
o
g
(
d
[
S
i
−
1
]
+
d
[
S
i
]
)
)
≤
O
(
2
∗
d
[
S
i
]
l
o
g
(
2
∗
d
[
S
i
]
)
)
O(d[S_{i-1}] + d[S_i]log(d[S_{i-1}] + d[S_i])) \le O(2*d[S_i]log(2*d[S_i]))
O(d[Si−1]+d[Si]log(d[Si−1]+d[Si]))≤O(2∗d[Si]log(2∗d[Si])),
而
d
[
S
i
]
≤
s
z
[
S
i
]
d[S_i]\le sz[S_i]
d[Si]≤sz[Si]忽略常数后, 对所有子树求和。 则时间复杂度为
Σ
i
O
(
s
z
[
S
i
]
l
o
g
(
s
z
[
S
i
]
)
=
Σ
O
(
s
z
[
S
]
l
o
g
(
s
z
[
S
]
)
\Sigma_i O(sz[S_i]log(sz[S_i]) = \Sigma O(sz[S]log(sz[S])
ΣiO(sz[Si]log(sz[Si])=ΣO(sz[S]log(sz[S]), 对于每一层的所有子树, 时间复杂度总和为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn), 点分治不超过
l
o
g
log
log 层, 总时间复杂度为
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)
所以,在如下代码中, 我们特别注意这一段话
vector<pair<int, int>> prior;
for (int i = head[u]; i; i = nxt[i]) { // 提前进行一次dfs, 确定遍历优先级
int v = ver[i];
if (del[v]) continue;
maxD = 0;
getDis(v, u, 0); // 第一次调用, 根据dis确定优先级,不改变其他内容, add定为0
prior.push_back({maxD, v});
}
sort(prior.begin(), prior.end()); // !!! 根据maxD改变遍历顺序, 才能保证时间复杂度
hack数据
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
const int INF = 0x3f3f3f3f;
const int MOD = 998244353;
const double PI = acos(-1.0);
typedef double db;
typedef long double ld;
int head[N], ver[N << 1], nxt[N << 1], tot;
int ans[N];
void add(int u, int v){
ver[++tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
namespace FFT{
const int MAX_LEN = 2e5 + 10;
const int LOG = __lg(MAX_LEN + 1);
struct cp
{
db r;db v;
friend cp operator +(cp a,cp b)
{
return (cp){a.r+b.r,a.v+b.v};
}
friend cp operator -(cp a,cp b)
{
return (cp){a.r-b.r,a.v-b.v};
}
friend cp operator *(cp a,cp b)
{
return (cp){a.r*b.r-a.v*b.v,a.r*b.v+a.v*b.r};
}
void operator /=(const int& b){r/=b;v/=b;}
};
int rev[LOG + 1][N];
cp rt[2][LOG + 1][N];
void init() // main函数中注意调用init
{
for(int d=1;d<=LOG;d++)
for(int i=1;i<(1<<d);i++)
rev[d][i]=(rev[d][i>>1]>>1)|((i&1)<<(d-1));
for(int o=0;o<=1;o++) // 预处理可以提高精度
for(int i=1,len=1;i<=LOG;i++,len<<=1)
for(int j=0;j<(1<<i);j++)
rt[o][i][j]=(cp){cos(j*PI/len),sin(j*PI/len)*(o?-1:1)};
}
void fft(cp* a,int len,int d,int o) // len必须为2的整数倍, 且len == (1 << d). o = 0为dft, 1为idft
{
for(int i=0;i<len;i++)
if(i<rev[d][i])swap(a[i],a[rev[d][i]]);
int i;int c2=1;
for(int i=1;i<len;i<<=1,c2++)
{
for(int j=0;j<len;j+=(i<<1))
{
int c=0;
for(int k=j;k<i+j;k++,c++)
{
cp a1=a[k];cp a2=a[k+i]*rt[o][c2][c];
a[k]=a1+a2;a[k+i]=a1-a2;
}
}
}
if(o)for(int i=0;i<len;i++)a[i]/=len;
}
};
FFT::cp x[N], y[N];
namespace VertexDivide{
int sz[N], d[N], cntThis[N], cntSum[N];
bool del[N];
int tmp, root, maxD;
void getGravity(int u, int fa, int n) {
sz[u] = 1;
int maxPart = 0;
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (v == fa || del[v]) continue;
getGravity(v, u, n);
sz[u] += sz[v];
maxPart = max(maxPart, sz[v]);
}
maxPart = max(maxPart, n - maxPart);
if (maxPart < tmp) {
tmp = maxPart;
root = u;
}
}
void getDis(int u, int fa, int add) {
d[u] = d[fa] + 1;
cntThis[d[u]] += add;
maxD = max(maxD, d[u]);
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (v == fa || del[v]) continue;
getDis(v, u, add);
}
}
void calc(int u) {
d[u] = 0; cntSum[0]++;
vector<pair<int, int>> prior;
for (int i = head[u]; i; i = nxt[i]) { // 提前进行一次dfs, 确定遍历优先级
int v = ver[i];
if (del[v]) continue;
maxD = 0;
getDis(v, u, 0); // 第一次调用, 根据dis确定优先级,不改变其他内容, add定为0
prior.push_back({maxD, v});
}
sort(prior.begin(), prior.end()); // !!! 根据maxD改变遍历顺序, 才能保证时间复杂度
maxD = 0;
for (int i = 0; i < prior.size(); i++) {
int maxD = prior[i].first, v = prior[i].second;
if (del[v]) continue;
getDis(v, u, 1); // 第二次调用, add定为1
int len = 1, d = 0;
while (len <= 2 * maxD) len <<= 1, d++;
for (int j = 0; j < len; j++) {
x[j].r = cntThis[j];
x[j].v = 0;
y[j].r = cntSum[j];
y[j].v = 0;
}
FFT::fft(x, len, d, 0);
FFT::fft(y, len, d, 0);
for (int j = 0; j < len; j++)
x[j] = x[j] * y[j];
FFT::fft(x, len, d, 1);
for (int j = 0; j < len; j++) {
ans[j] += (ll)(x[j].r + 0.5);
}
// 统计答案, 清空当前子树
for (int j = 0; j <= maxD; j++) {
cntSum[j] += cntThis[j];
cntThis[j] = 0;
}
}
// 清空整棵树
for (int j = 0; j <= maxD; j++) {
cntSum[j] = 0;
}
}
void divide(int u, int n) {
tmp = INF;
getGravity(u, 0, n);
u = root;
calc(u);
del[u] = 1;
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (del[v]) continue;
divide(v, sz[v]);
}
}
}
void solve() {
int n;
cin >> n;
FFT::init();
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
VertexDivide::divide(1, n);
vector<int> vis(n + 1), prime;
vis[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) prime.push_back(i);
for (auto p : prime) {
if (i * p > n) break;
vis[i * p] = 1;
if (i % p == 0) break;
}
}
int cnt = 0, cntAll = 0;
for (int i = 0; i <= n; i++) {
cntAll += ans[i];
if (!vis[i]) cnt += ans[i];
}
cout << (1.0 * cnt / cntAll) << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}