树分治练习题题解

/*

dis数组维护的是前i-1的子树所有节点的代价和以及在不超过该代价下最大的收益。

tdis数组维护的是第i棵子树所有节点的代价和以及相应的收益。

=================================================================================

sqrt(N)的原因说简单点就是这种不去重方式的启发式合并，计算量是O(N*sqrt(N))的，。
=================================================================================
**/
#include<bits/stdc++.h>
using namespace std;
#define MAXN 20010
struct edge {
int to, next;
int c, b;
edge(int to = 0, int next = 0, int c = 0, int b = 0): to(to), next(next), c(c), b(b) {}
} es[MAXN * 2];
int n, C;

void add(int from, int to, int c, int d) {
}
void init() {
ecnt = 0;

}
int siz[MAXN], maxsonsiz[MAXN], G, subn;
bool vis[MAXN];
int ans;
/*存重心子节点的编号，重心到子节点的边的编号，以子节点为根的子树的大小*/
struct node {
int id, eid, siz;
node(int id = 0, int eid = 0, int siz = 0): id(id), eid(eid), siz(siz) {}
bool operator <(node nd)const {
return siz < nd.siz;
}
} pro[MAXN];
/*存从重心往下搜索路径上消耗的代价和收益*/
struct record {
int c, b;
record(int c = 0, int b = 0): c(c), b(b) {}
} dis[MAXN], tdis[MAXN], tmp[MAXN];
int pcnt, dis_siz, tdis_siz, tmp_siz;
/*下面main函数中一定要记得调用这个函数初始化*/
void init2() {
G = 0, subn = n, maxsonsiz[G] = subn;
memset(vis, 0, sizeof(vis[0]) * (n + 3));
ans = 0;
dis_siz = tdis_siz = 0;
}
/*模板：找重心函数*/
void find_center(int root, int par) {
siz[root] = 1, maxsonsiz[root] = 0;
for(int i = head[root]; i; i = es[i].next) {
int to = es[i].to;
if(to != par && !vis[to]) {
find_center(to, root);
siz[root] += siz[to];
maxsonsiz[root] = max(maxsonsiz[root], siz[to]);
}
}
maxsonsiz[root] = max(maxsonsiz[root], subn - siz[root]);
if(maxsonsiz[root] < maxsonsiz[G])G = root;
}
/*计算以root为根的子树的大小*/
int getsiz(int root, int par) {
int res = 1;
for(int i = head[root]; i; i = es[i].next) {
int to = es[i].to;
if(to != par && !vis[to]) {
res += getsiz(to, root);
}
}
return res;
}
/*

*/
void getdis(int root, int par, record r) {
if(r.c>C)return;
tdis[++tdis_siz] = r;
for(int i = head[root]; i; i = es[i].next) {
int to = es[i].to;
if(to != par && !vis[to]) {
getdis(to, root, record(r.c + es[i].c, r.b + es[i].b));
}
}
}

bool cmp(record a, record b) {
if(a.c == b.c)return a.b < b.b;
else return a.c < b.c;
}

int bs(int l, int r, int v) {
int low = l - 1, high = r + 1, mid;
while(low < high - 1) {
mid = (low + high) / 2;
if(dis[mid].c <= v)low = mid;
else high = mid;
}
return low;
}

void find_ans(int center, int par) {
pcnt = 0;
/*预处理每个儿子节点，然后再启发式合并。*/
for(int i = head[center]; i; i = es[i].next) {
int to = es[i].to;
if(to != par && !vis[to]) {
pro[++pcnt] = node(to, i, getsiz(to, center));
}
}
sort(pro + 1, pro + pcnt + 1);
for(int i = 1; i <= pcnt; ++i) {
tdis_siz = 0;
getdis(pro[i].id, center, record(es[pro[i].eid].c, es[pro[i].eid].b));
if(i > 1) {
/*对于当前子树的所有tdis来说，二分查找出前i-1棵子树的dis*/
for(int j = 1; j <= tdis_siz; ++j) {
int pos = bs(1, dis_siz, C - tdis[j].c);
if(pos != 0)ans = max(ans, tdis[j].b + dis[pos].b);
}
}
/*这里要非常注意，如果答案是从重心到子树中某个节点的链，就用这条语句来更新答案。*/
for(int j = 1; j <= tdis_siz; ++j)if(tdis[j].c <= C)ans = max(ans, tdis[j].b);
/*这里一定要记得排序，因为后面要做启发式合并。*/
sort(tdis + 1, tdis + tdis_siz + 1, cmp);
tmp_siz = 0;/*一下是归并排序思想做启发式合并，官方库的merge还是不太方便，自己实现最靠谱。*/
for(int p1 = 1, p2 = 1; p1 <= dis_siz || p2 <= tdis_siz;) {
if(p1 <= dis_siz && p2 <= tdis_siz) {
if(dis[p1].c < tdis[p2].c)tmp[++tmp_siz] = dis[p1++];
else if(dis[p1].c == tdis[p2].c) {
if(dis[p1].b < tdis[p2].b)tmp[++tmp_siz] = dis[p1++];
else tmp[++tmp_siz] = tdis[p2++];
}
else tmp[++tmp_siz] = tdis[p2++];
}
else if(p1 <= dis_siz)tmp[++tmp_siz] = dis[p1++];
else tmp[++tmp_siz] = tdis[p2++];
}
/*这里要非常注意，dis数组收益b需要维护前i棵树的前缀和*/
for(int j = 1; j <= tmp_siz; ++j)dis[j].c = tmp[j].c, dis[j].b = max(dis[j - 1].b, tmp[j].b);
dis_siz = tmp_siz;
}
dis_siz = 0;
}
/*模板：树分治*/
void solve(int root, int par) {
find_center(root, par);
assert(G != 0);
vis[G] = 1;
find_ans(G, 0);
for(int i = head[G]; i; i = es[i].next) {
int to = es[i].to;
if(!vis[to]) {
G = 0, subn = siz[to], maxsonsiz[G] = subn;
solve(to, G);
}
}
}

int main() {
//    freopen("h.in", "r", stdin);
int T;
for(scanf("%d", &T); T--;) {
scanf("%d", &n);
init();
for(int i = 1, a, b, c, d; i < n; ++i) {
scanf("%d%d%d%d", &a, &b, &c, &d);
}