QTREE4 - Query on a tree IV
#tree
You are given a tree (an acyclic undirected connected graph) with N nodes, and nodes numbered 1,2,3…,N. Each edge has an integer value assigned to it(note that the value can be negative). Each node has a color, white or black. We define dist(a, b) as the sum of the value of the edges on the path from node a to node b.
All the nodes are white initially.
We will ask you to perfrom some instructions of the following form:
C a : change the color of node a.(from black to white or from white to black)
A : ask for the maximum dist(a, b), both of node a and node b must be white(a can be equal to b). Obviously, as long as there is a white node, the result will alway be non negative.
Input
In the first line there is an integer N (N <= 100000)
In the next N-1 lines, the i-th line describes the i-th edge: a line with three integers a b c denotes an edge between a, b of value c (-1000 <= c <= 1000)
In the next line, there is an integer Q denotes the number of instructions (Q <= 100000)
In the next Q lines, each line contains an instruction “C a” or “A”
Output
For each “A” operation, write one integer representing its result. If there is no white node in the tree, you should write “They have disappeared.”.
Example
Input:
3
1 2 1
1 3 1
7
A
C 1
A
C 2
A
C 3
A
Output:
2
2
0
They have disappeared.
Some new test data cases were added on Apr.29.2008, all the solutions have been rejudged.
树的边有正负权重,然后告诉你树上有一些 白点 和 黑点,有两种操作
1.改变点的颜色,即白变黑,黑变白
2.问树上两个白点之间路径权重和最大数多少,可以自己到自己,即路径权重为0。也就是说只要有一个白点路径权重就>0,。全是黑点输出 They have disappeared.
动态点分治,和点分治一样,把树上的路径分为经过 重心 和不经过 重心 两种。
先进行点分治,和一般的点分治操作差不多,就是我们要多一个 fa 数组来保存这个重心的父重心是谁,用来下面的修改点的颜色的操作。
每到一个重心t,就计算它的子树中的点到它的路径权值,保存在对应的子树重心的优先队列里。我们用p[x]来保存(x为子树重心)。fa[x] = t,子中心指向父重心。
计算子树中各个点到重心的权值时,我们建一张边表,起始点存子树的点,目标点存子树的重心x,权重存子树到重心t的路径权值和,用于下面的修改点的颜色。
然后经过这个重心的最大路径权值就是从他的各个子树中找出最大的路径,然后把最大的和第二大的加起来。我们用maxn[t](即重心为t)优先队列 来保存它的子树中的点到它的最大路径。注意添加一个0到maxn[t]中,即自己到自己这种情况。
然后用一个ans优先队列来保存每个每个重心的答案,ans中最大的值就是我们要找的答案。
(上面这段一定要理解了)
修改点的操作,我们只讨论白变黑,黑变白是类似的。
为了方便修改,对于p数组,maxn数组,ans我们都把添加进来的值都放在q这个优先队列里,要删除的值都发那个在d这个优先队列里,当d和p的最大值都一样时,q和d都出栈。
还是来讨论一下吧。
比如p数组,p[x]保存了x重心所在的子树中的点到父重心t的距离。如果这里面有一条边不能用了,我们把它加到d数组里面。如果这条边不是p[x]里最大值,则p[x]对于求经过父重心t的路径权重最大值没有任何改变,我们就不管他。如果是最大值,q和d同时出栈这个元素,p[x]提供的路径最大值改变。所以manx[t]也要进行改变,即删除原来p[x]里的最大值,加入新的p[x]里的最大值。
如果maxn[t]改变了,ans也有可能改变,所以ans里删除原来maxn[t],加入新的maxn[t]
点 x 由白变黑就相当于 x 到它的所有父重心的路径都走不了了,而且自己也无法到达自己。
我们先来处理后面这一种情况,后面这种情况比较简单,只需要在maxn[x].d里添加0表示删除掉这个点到自己这种情况。如果改变了则修改ans中的情况
然后是前面一种情况,这时候就要用到之前的边表了。
我们跑一边边表,边表的起始点是点 x,目标点是x的各个父重心 t ,我们把边重添加到p[t].d当中,表示删掉这段路径,如果有最大值发生变化就修改maxn[t],如果manx[t]发生变化就修改ans。跑完边表我们就修改了 x 到它所有父重心的路径权值。
询问的时候只要输出ans.top()就可以了
动态点分治的实质就是建立一棵重心树,每个子中心指向自己的父重心。
比如说一棵树,长成正方形的样子,他的重心就是正方形的重心。
假设重心向正方形四个角都延伸出一棵子树,这四棵子树又有一个重心,我们让这些子树的重心指向父重心。然后这样不断分治,建立一棵重心树。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
const int INF=0x3f3f3f3f;
struct node1{
int v,nx,w;
}edge[MAXN<<1],EDGE[MAXN<<4];
//注意这个<<4,重心树的深度为log2(n) ,就是说每个点到其父重心的边总共大概有nlon2(n)条
int head[MAXN],tot,HEAD[MAXN],col[MAXN],TOT;//edge是题目给的数据的表。EDGE是后来建立的点到各个父重心的边表
inline void add(int u,int v,int w)
{
edge[++tot].v = v;
edge[tot].w = w;
edge[tot].nx = head[u];
head[u] = tot;
}
inline void ADD(int u,int v,int w)
{
EDGE[++TOT].v = v;
EDGE[TOT].w = w;
EDGE[TOT].nx = HEAD[u];
HEAD[u] = TOT;
}
int sz[MAXN],size,root,maxsubtree,vis[MAXN];
void getroot(int x,int fa) //找重心
{
sz[x] = 1;
int maxn = 0,v;
for (int i = head[x];i;i = edge[i].nx)
{
v = edge[i].v;
if (vis[v] || v == fa) continue;
getroot(v,x);
sz[x] += sz[v];
maxn = max(maxn,sz[v]);
}
maxn = max(maxn,size - sz[x]);
if (maxn < maxsubtree)
{
maxsubtree = maxn;
root = x;
}
}
int fa[MAXN],cnt;//cnt统计白点个数
struct node{
priority_queue<int> q,d;
void add(int x){q.push(x);}
void del(int x){d.push(x);}
int top()
{
for(;;){
if(q.empty())return -INF;
else if(!d.empty() && q.top() == d.top())q.pop(),d.pop();
else return q.top();
}
}
int toptwo(){//返回优先队列里最大的两个值的和
int x = top();
del(x);
int y = top();
add(x);
if(y == -INF) return x == -INF?max(x,0):0;
return max(x+y,0);
return 1;
}
}p[MAXN],maxn[MAXN],ans;
void dfs(int x,int fx,int dis,int center)
{
p[center].add(dis);//把子树中的点到父重心的距离权值添加到子p[center]当中
ADD(x,center,dis);//建立点到它父重心的边表,用于后面的修改操作
int v;
for (int i = head[x];i;i = edge[i].nx)
{
v = edge[i].v;
if (vis[v] || v == fx) continue;
dfs(v,x,dis + edge[i].w,center);
}
}
void build(int x,int fx)
{
fa[x] = fx; //存下每个子中心的父重心
vis[x] = 1;
int v;
maxn[x].add(0); //添加自己到自己情况
for (int i = head[x];i;i = edge[i].nx)
{
v = edge[i].v;
if (vis[v]) continue;
maxsubtree = 2147483647,size = sz[v];//寻找重心初始化
getroot(v,x); //寻找子树的重心
dfs(v,x,edge[i].w,root); //dfs跑子树到重心x的路径权值,保存在子树的重心p[center]处
maxn[x].add(p[root].top()); //存下这颗子树的最大路径权值
build(root,x); //建立子树的重心树
}
ans.add(maxn[x].toptwo()); //统计经过重心的路径的答案
}
void change(int x)
{
col[x] = !col[x];
if (col[x] == 1)//白变黑
{
cnt--;//白点数目-1
int a = maxn[x].toptwo(); //自己到自己的情况删去
maxn[x].del(0);
int b = maxn[x].toptwo();
if (a != b) ans.del(a),ans.add(b);
int v;
for (int i = HEAD[x];i;i = EDGE[i].nx)//点到其各个父重心的路径权值删去
{
v = EDGE[i].v;
a = p[v].top();
p[v].del(EDGE[i].w);
b = p[v].top();
if (a != b)
{
int c = maxn[fa[v]].toptwo();
maxn[fa[v]].del(a);
maxn[fa[v]].add(b);
int d = maxn[fa[v]].toptwo();
if (c != d)
{
ans.del(c);
ans.add(d);
}
}
}
}
else
{
cnt++;
int a = maxn[x].toptwo();
maxn[x].add(0);
int b = maxn[x].toptwo();
if (a != b) ans.del(a),ans.add(b);
int v;
for (int i = HEAD[x];i;i = EDGE[i].nx)
{
v = EDGE[i].v;
a = p[v].top();
p[v].add(EDGE[i].w);
b = p[v].top();
if (a != b)
{
int c = maxn[fa[v]].toptwo();
maxn[fa[v]].del(a);
maxn[fa[v]].add(b);
int d = maxn[fa[v]].toptwo();
if (c != d)
{
ans.del(c);
ans.add(d);
}
}
}
}
}
int main()
{
int n;
scanf("%d",&n);
cnt = n;
for (int i = 1,u,v,w;i<n;i++)//建立树
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w); add(v,u,w);
}
maxsubtree = 2147483647,size = n;
getroot(1,0);//找树的重心
build(root,0);//建立重心树
int q;
scanf("%d",&q);
char opt[19];
while (q--)
{
scanf("%s",opt);
if (opt[0] == 'C')
{
int x;
scanf("%d",&x);
change(x);
}
else
{
if (cnt == 0) printf("They have disappeared.\n");
else printf("%d\n",ans.top());
}
}
return 0;
}
绝对是我目前写过的最详细的题解博客了,动态点分治orz
尤其是那个<<4,坑死我了,蒟蒻流下了不争气的泪水