并查集-水晶球(nkoj2105)
题意分析
动态维护[l,r]区间的和
并查集与前缀和结合使用
利用带序号的并查集与前缀和
这里将i节点的到它的父亲fi这一区间的和记作dis[i]
,将x的前缀和记作
有如下性质:
- x的前缀和与y的前缀和之差,即[y,x]区间的和为p
- [fx,x]区间的和为dis[x]
- [fy,y]区间的和为dis[y]
可以得到合并的时候:
-
即[fx,fy]区间([fx,ffx]区间)的和为
-dis[x] + dis[y] + p
这样维护序号,如果两个节点属于同一堆,则可得到他们组成的区间的和
代码
//
// Created by rv on 2018/4/20.
//
#include "cstdio"
#include "map"
using namespace std;
struct UF {
int count;
int* father;
int* dis;
UF(int t) {
count = t;
father = new int[t];
dis = new int[t];
for (int i = 0; i < t; i++) {
father[i] = i;
dis[i] = 0;
}
}
int get_father(int x) {
if (father[x] == x) {
return x;
} else {
int tmp = get_father(father[x]);
dis[x] += dis[father[x]];
father[x] = tmp;
return father[x];
}
}
void merge(int x, int y, int p) {
int fx = get_father(x);
int fy = get_father(y);
father[fx] = fy;
dis[fx] = - dis[x] + dis[y] + p;
}
void check(int x, int y, int p) {
int fx = get_father(x);
int fy = get_father(y);
if (fx == fy) {
int can_p = dis[x] - dis[y];
if (can_p != p) {
printf("Bug Detected %d\n", can_p);
} else {
printf("Accept\n");
}
} else {
merge(x, y, p);
printf("Accept\n");
}
}
void print() {
printf("father: ");
for (int i = 0; i < count; i++) {
printf("%d ", father[i]);
}
printf("\n");
// printf("size: ");
// for (int i = 0; i < count; i++) {
// printf("%d ", size[i]);
// }
// printf("\n");
printf("dis: ");
for (int i = 0; i < count; i++) {
printf("%d ", dis[i]);
}
printf("\n");
}
};
int main() {
// freopen("1206/data2.in", "r", stdin);
// freopen("data2.out", "w", stdout);
int M, l, r, p;
scanf("%d", &M);
UF uf(2 * M + 1);
map<int, int> mp;
while(M--) {
scanf("%d%d%d", &l, &r, &p);
// printf("l=%d r=%d p=%d\n", l, r, p);
if (mp.find(r) == mp.end()) {
mp[r] = mp.size();
}
if (mp.find(l - 1) == mp.end()) {
mp[l - 1] = mp.size();
}
uf.check(mp[r], mp[l - 1], p);
// uf.print();
}
return 0;
}
PS
由于l,r太大,所以用map映射(离散化)后再用并查集处理较好,具体做法就是mp[i]=mp.size()