BZOJ-1095 Hide 捉迷藏 模型建立+线段树
题意:给定一棵树,每个节点刚开始的时候都是黑色的,现在有Q组操作,要么是询问当前的树中距离最远的两个黑色的距离是多大,要么是改变某一点的颜色。
解法:该题看论文+参考代码搞了一天,感觉模型的转化已经求解的过程都非常的巧妙。
1.将这棵树有树形结构选择一个根节点再通过dfs搜索转化线性结构,然后将距离问题用括号来描述。
2.使用括号描述后,发现任意两个点的距离是通过对之间的括号序列进行某种消除从非稳定态到达一个稳定态,然后在稳定态中得到距离信息。
3.发现括号序列是能够进行合并的,所以线段树被派上了用场。而这明显又是一个动态规划的合并过程,关于区间的线段树题无非要维护这么几个量,单独的区间、左连续的区间、右连续的区间等。由于每个稳定态需要记录两个信息,左括号数量和右括号数量,所以维护起来不太方便,而一个良好的变化能够使得长度之间能够互相推导,因此只需要维护以上说的三个量的一些长度,并且保留下当前区间的稳定态用于计算整个区间的长度即可。
总之各种神......
岛娘的题解:http://www.shuizilong.com/house/archives/bzoj-1095-zjoi2007hide-%E6%8D%89%E8%BF%B7%E8%97%8F/
代码如下:
#include <cstdlib> #include <cstring> #include <cstdio> #include <iostream> #include <algorithm> using namespace std; int N, Q, cnt; const int MaxN = 100005; const int MaxE = 500005; const int INF = 0x3fffffff; int v[MaxN*3], pos[MaxN]; char sta[MaxN]; struct Edge { int v, next; }eg[MaxE<<1]; int idx, head[MaxN]; /* 什么叫进行一个处理形如 "[[]][[][]][" 对于左边的括号串进行一个匹配的消除即为一个 处理括号的意思: '[' 代表层数加1 ']' 代表层数减1 如果在一个dfs的过程中先下降然后上升那么从某点x(在第i层)再次回到第i层意味着从x 到之后的节点的路径长度将与前面的这个下降-上升过程无关。前面所说的括号的括号匹配 消除就是将这些无用的过程删除掉,剩下的 "]][[[[" 串的长度即是所求的距离。 约定使用数字2代表'[',数字5代表']',那么上串变为了225555,而线段树中要统计的就是 区间中的关于2和5的长度,以及区间合并时2和5的作用所需的变量。 一个25串合法就是要求他必须出现在两个黑点之间 任意一个合法区间[l, r]可使用一个数对来表示:(a, b)表示该区间经处理后有a个2,b个5 实际上就是从 l 节点到 r 节点要上a层,再下b层,根据它的得来可知这样走不饶路,且为 最短路。那么现在考虑两个区间的合并问题:已知区间[A, B]为(a1, b1), [B, C]为(a2, b2) 实际上现在就是要计算一个从A到C的距离是多大的问题,显然这里并不要求B点必须为黑点 但是A,D必须是黑点。现在考虑在B的处的情况,从A过来是以下降的姿态过来,而从B到C又是 上升的姿态上去,根据前面消除括号的原理,这里面还有一些括号是能够被消除掉的,那么这 里其实就要看是左边的下降多还是右边的上升多,根据不同情况来取值,写成表达式如下: if (b1 < a2) 合并后为 (a1+a2-b1, b2),合并后的长度为 (a1-b1)+(a2+b2) if (b1 >= a2) 合并后为 (a1, b1-a2+b2),合并后的长度为 (a1+b1)+(b2-a2) 实际上最后的长度就可以写为:max((a1-b1)+(a2+b2), (a1+b1)+(b2-a2)),这样就不用考虑他 的们位置关系,而只需要关心他们的各自长度 因此在合并区间的时候我们需要保留一个最优的(a-b)和(b-a)来用于合并 当然并不是所有合并后的解就一定是最优解,因此还需记录单个区间的最优解,合并之后的结果 也要送到父亲的该节点进行比较。其次还要保留对整个区间的一个描述量,以应对覆盖整个孩子 区间的合并方案。 综上一个区间的任意两点间黑点距离可以出现以下情况: 1.直接从从左右区间推得 2.包含左区间右起的一部分以及右区间左起的一部分 */ struct Node { // 以下所用区间均指经过处理后的区间,即所指25串形式为 ]]][[[ 的形式 int C2; // 记录[l, r]剩余']'的长度,即25串左边2的长度 int C5; // 记录[l, r]剩余'['的长度,即25串右边5的长度 int M25; // 记录[l, r]区间两个黑点之间距离的最远值,即符合要求的最长的25 int L25; // 记录包含l的最长的25串区间,不要求l左边一定为黑点,右端点必须是黑点 int R25; // 记录包含r的最长的25串区间,不要求r右边一定为黑点,左端点必须是黑点 int L5; // 记录包含l的最大的5与2的长度之差区间,右端点必须是黑点 int R2; // 记录包含r的最大的2与5的长度之差区间,左端点必须是黑点 void init(int x); }e[MaxN*12]; void Node::init(int x) { C2 = v[x] == -2; C5 = v[x] == -5; M25 = -INF; if (v[x] > 0 && !sta[v[x]]) { L25 = R25 = L5 = R2 = 0; } else { L25 = R25 = L5 = R2 = -INF; } } void insert(int a, int b) { eg[idx].v = b; eg[idx].next = head[a]; head[a] = idx++; } void dfs(int u) { v[cnt++] = -5; // 后驱括号 "[" pos[u] = cnt; v[cnt++] = u; for (int i = head[u]; ~i; i = eg[i].next) { if (!(~pos[eg[i].v])) { dfs(eg[i].v); } } v[cnt++] = -2; // 前驱括号 "]" } void push_up(int p, int LL, int RR) { int l = p<<1, r = p<<1|1; // 对本身整个区间的一个更新,计算坐标需要进行判定 e[p].C2 = e[l].C5 > e[r].C2 ? e[l].C2 : e[l].C2+e[r].C2-e[l].C5; e[p].C5 = e[l].C5 > e[r].C2 ? e[r].C5+e[l].C5-e[r].C2 : e[r].C5; // e[p].C2 = e[l].C2 + max(0, e[r].C2-e[l].C5); // e[p].C5 = e[r].C5 + max(0, e[l].C5-e[r].C2); // 要么从左右孩子直接得到,要么一边取一部分,使用一个max来避开求区间合并后的坐标 e[p].M25 = max(max(e[l].M25, e[r].M25), max(e[l].R25+e[r].L5, e[r].L25+e[l].R2)); // 要求左边连续,则要么从左孩子得到,要么同样是一个max函数来求长度,原理与M25相同 // e[l].C2+e[l].C5+e[r].L5 相当于左区间取(a+b),右区间取(b-a) // e[l].C2-e[l].C5+e[r].R25 相当于左区间取(a-b),右区间取(a+b) e[p].L25 = max(e[l].L25, max(e[l].C2+e[l].C5+e[r].L5, e[l].C2-e[l].C5+e[r].L25)); e[p].R25 = max(e[r].R25, max(e[r].C2+e[r].C5+e[l].R2, e[r].C5-e[r].C2+e[l].R25)); // 这里算是对两个区间差值的求和,直接对应相加即可,不需要考虑消除带来的影响 e[p].L5 = max(e[l].L5, e[l].C5-e[l].C2+e[r].L5); e[p].R2 = max(e[r].R2, e[r].C2-e[r].C5+e[l].R2); } void build(int p, int l, int r) { if (l != r) { int mid = (l + r) >> 1; build(p<<1, l, mid); build(p<<1|1, mid+1, r); push_up(p, l, r); } else { e[p].init(l); // 初始化叶子节点 } } void modify(int p, int l, int r, int x) { if (l == r) { e[p].init(l); } else { int mid = (l + r) >> 1; if (x <= mid) modify(p<<1, l, mid, x); else modify(p<<1|1, mid+1, r, x); push_up(p, l, r); } } int main() { int a, b, black; char op[5]; while (scanf("%d", &N) != EOF) { cnt = idx = 0; black = N; memset(head, 0xff, sizeof (head)); memset(pos, 0xff, sizeof (pos)); memset(sta, 0, sizeof (sta)); for (int i = 1; i < N; ++i) { scanf("%d %d", &a, &b); insert(a, b), insert(b, a); } dfs(1); build(1, 0, cnt-1); scanf("%d", &Q); while (Q--) { scanf("%s", op); if (op[0] == 'G') { if (black == 0) puts("-1"); else if (black == 1) puts("0"); else printf("%d\n", e[1].M25); } else { scanf("%d", &a); black += sta[a] ? 1 : -1; sta[a] = !sta[a]; modify(1, 0, cnt-1, pos[a]); } } } return 0; }