[题解] hdu 3276 Graph and Queries(Splay)

- 传送门 -

 http://acm.hdu.edu.cn/showproblem.php?pid=3726
 

#Graph and Queries

Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4170 Accepted Submission(s): 980

Problem Description

You are given an undirected graph with N vertexes and M edges. Every vertex in this graph has an integer value assigned to it at the beginning. You're also given a sequence of operations and you need to process them as requested. Here's a list of the possible operations that you might encounter:

  1. Deletes an edge from the graph.
    The format is [D X], where X is an integer from 1 to M, indicating the ID of the edge that you should delete. It is guaranteed that no edge will be deleted more than once.
  2. Queries the weight of the vertex with K-th maximum value among all vertexes currently connected with vertex X (including X itself).
    The format is [Q X K], where X is an integer from 1 to N, indicating the id of the vertex, and you may assume that K will always fit into a 32-bit signed integer. In case K is illegal, the value for that query will be considered as undefined, and you should return 0 as the answer to that query.
  3. Changes the weight of a vertex.
    The format is [C X V], where X is an integer from 1 to N, and V is an integer within the range [-106, 106].

The operations end with one single character, E, which indicates that the current case has ended.
For simplicity, you only need to output one real number - the average answer of all queries.

Input

There are multiple test cases in the input file. Each case starts with two integers N and M (1 <= N <= 2 * 104, 0 <= M <= 6 * 104), the number of vertexes in the graph. The next N lines describes the initial weight of each vertex (-106 <= weight[i] <= 106). The next part of each test case describes the edges in the graph at the beginning. Vertexes are numbered from 1 to N. The last part of each test case describes the operations to be performed on the graph. It is guaranteed that the number of query operations [Q X K] in each case will be in the range [1, 2 * 105], and there will be no more than 2 * 105 operations that change the values of the vertexes [C X V].

There will be a blank line between two successive cases. A case with N = 0, M = 0 indicates the end of the input file and this case should not be processed by your program.

Output

For each test case, output one real number – the average answer of all queries, in the format as indicated in the sample output. Please note that the result is rounded to six decimal places.

Sample Input

3 3
10
20
30
1 2
2 3
1 3
D 3
Q 1 2
Q 2 1
D 2
Q 3 2
C 1 50
Q 1 1
E

3 3
10
20
20
1 2
2 3
1 3
Q 1 1
Q 1 2
Q 1 3
E

0 0

Sample Output

Case 1: 25.000000
Case 2: 16.666667
 

Hint

For the first sample:
D 3 -- deletes the 3rd edge in the graph (the remaining edges are (1, 2) and (2, 3))
Q 1 2 -- finds the vertex with the second largest value among all vertexes connected with 1. The answer is 20.
Q 2 1 -- finds the vertex with the largest value among all vertexes connected with 2. The answer is 30.
D 2 -- deletes the 2nd edge in the graph (the only edge left after this operation is (1, 2))
Q 3 2 -- finds the vertex with the second largest value among all vertexes connected with 3. The answer is 0 (Undefined).
C 1 50 -- changes the value of vertex 1 to 50.
Q 1 1 -- finds the vertex with the largest value among all vertex connected with 1. The answer is 50.
E -- This is the end of the current test case. Four queries have been evaluated, and the answer to this case is (20 + 30 + 0 + 50) / 4 = 25.000.

For the second sample, caution about the vertex with same weight:
Q 1 1 – the answer is 20
Q 1 2 – the answer is 20
Q 1 3 – the answer is 10

[Statistic](http://acm.hdu.edu.cn/statistic.php?pid=3726) | [Submit](http://acm.hdu.edu.cn/submit.php?pid=3726) | [Discuss](http://acm.hdu.edu.cn/discuss/problem/list.php?problemid=3726) | [Note](http://acm.hdu.edu.cn/note/note.php?pid=3726)
  ### - 题意 -  给你一个 n 点 m 边的无向图, 然后有若干条操作:  1. D x : 删掉第 x 条边.  2. Q x k : 询问和点 x 相连的点中, 权值第 k 大的 (询问可能非法).  3. C x v : 将点 x 的权值改为 v.  计算每次 Q 操作得到的答案之和 / Q 操作次数(保留六位小数).   ### - 思路 -  断边来使一棵树一分为二是是什么鬼操作...  于是我们可以从末状态开始倒推, 断边操作就转化为将两棵树启发式(暴力)合并了.  更改权值的操作就先删除再插入好了.  所以我们就先记录下每一个操作, 每个节点的更改用一条链表记录, 树的合并判断要两节点是否在一棵树上, 可以用并查集也可以直接查询根节点, 然后先模拟出末状态再一步一步改回初状态.    细节见代码.

 PS:
 1. 注释掉的部分是使用并查集的方法~
 2. 因为询问的是第 k 大节点, 本着能不计算就不计算的原则, 我维护了一个左大右小的二叉树...
 

- 代码 -

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int M = 6e4 + 50;
const int N = 2e4 + 50;
const int maxn = 4e5 + 50;

char qch[maxn][5];
int qx[maxn], qy[maxn]; //记录询问
int bu[M], bv[M], bflag[M], bpre[N]; //记录每一条边
int key[N], size[N], c[N][2], f[N]; //记录树上节点
int v[maxn], head[N], NEXT[maxn]; //记录节点权值的链表

int rt, n, m, sz, cnt, cas, tr, k;
double ans;

void read(int &x) {
	x = 0; int f = 1; char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-') f= -1; ch = getchar(); }
	while (ch <= '9' && ch >= '0') { x = x*10 + ch - '0'; ch = getchar(); }
	x *= f;
}

void Init() {
//	memset(bpre, 0, sizeof bpre);
	memset(head, 0, sizeof head);
	k = rt = sz = cnt = 0;
	ans = 0;
}

void Pushup(int x) {
	if (x) size[x] = size[c[x][0]] + size[c[x][1]] + 1;
}

void rotate(int x) {
	int y = f[x], z = f[y];
	int a = c[y][1] == x, b = a^1;
	if (z) c[z][c[z][1] == y] = x;
	f[x] = z, f[y] = x, f[c[x][b]] = y;
	c[y][a] = c[x][b], c[x][b] = y;
	Pushup(y), Pushup(x);
}

void Splay(int x, int g) {
	if (x != g)
		while (f[x] != g)
			rotate(x);
}

void New_node(int &t, int i, int fa, int val) {
	t = i;
	key[t] = val;
	size[t] = 1;
	f[t] = fa;
	c[t][0] = c[t][1] = 0;
}

void Add_node(int a, int x) {
	v[++ sz] = x;
	NEXT[sz] = head[a];
	head[a] = sz;
}

void Insert(int x) {
	int t = rt;
	if (!t) {
		c[x][0] = c[x][1] = 0;
		f[x] = 0;
		size[x] = 1;
		rt = x;
		return;
	}
	 while (c[t][key[t] > key[x]])
        t = c[t][key[t] > key[x]];
     New_node(c[t][key[t] > key[x]], x, t, key[x]);
     Splay(c[t][key[t] > key[x]], 0);
	 rt = x;
}

void Travel(int x) {
	if (!x) return;
	Travel(c[x][0]);
	Travel(c[x][1]);
	Insert(x);
	//结合Insert函数来看, 我们会遍历一棵树将它的每一个节点都插入另一棵树实现合并
	//插入操作是会把插入节点旋到根的(或者被插入的是一棵空树时, 插入的节点成为根)
	//所以每一次Insert的rt都是不同的, 要在Insert中改变rt的值
}

int Find(int x) {
	while (f[x]) x = f[x];
	return x;
//	return bpre[x] ? bpre[x] = Find(bpre[x]) : x;
}

void Union(int u, int v) {
	int x = Find(u), y = Find(v);
	if (x == y) return; //在一棵树上
//	bpre[x] = y;
	Splay(x, 0), Splay(y, 0);
	if (size[x] > size[y])
		swap(x, y); //启发式合并
	rt = y;	
	Travel(x); //遍历 + 单点插入
}

int Get_kth(int t, int k) {
	if (size[c[t][0]] + 1 == k)
		return t;
	else if (size[c[t][0]] >= k)
		return Get_kth(c[t][0], k);
	else
		return Get_kth(c[t][1], k - size[c[t][0]] - 1);
}

int Pre(int t) {
	return c[t][0] ? Pre(c[t][0]) : t;
}

void Delete(int t) {
	Splay(t, 0);
	if (!c[t][0] || !c[t][1]) {
		rt = c[t][0] + c[t][1];
		f[rt] = 0;
		return;
	}
	int y = Pre(c[t][1]);
	Splay(y, t);
	f[c[t][0]] = y;
	c[y][0] = c[t][0];
	Pushup(y);
	f[y] = 0;
	rt = y;
	//将rt的后继节点y作为它的右儿子, 再把rt的左儿子作为y的左儿子, 删除rt并维护
}

int main(){
	while (~scanf("%d %d", &n, &m)) {
		if ((!n) && (!m)) return 0;
		cas ++;
		Init();
		
		for (int i = 1, x; i <= n; i++)
			read(x), Add_node(i, x);
				
		for (int i = 1; i <= m; i++)
			read(bu[i]), read(bv[i]), bflag[i] = 1;
  	
		while (scanf("%s", qch[k]) == 1) {
			if (qch[k][0] == 'E') break;
			if (qch[k][0] == 'D')
				read(qx[k]), bflag[qx[k]] = 0;
			else if (qch[k][0] == 'Q')
				read(qx[k]), read(qy[k]);
			else {
				read(qx[k]), read(qy[k]);
				Add_node(qx[k], qy[k]);
			}
			k ++;
		}
      for (int i = 1; i <= n; i ++)
			New_node(rt, i, 0, v[head[i]]);

		for (int i = 1; i <= m; i ++)
			if (bflag[i]) Union(bu[i], bv[i]);
 		
       for (int i = k - 1; i >= 0; i --) {
			if (qch[i][0] == 'Q') {
				Splay(qx[i], 0);
				cnt ++;
				if (qy[i] > size[qx[i]] || qy[i] <= 0) //非法询问
					continue;
				ans += key[Get_kth(qx[i], qy[i])];
			}
			else if (qch[i][0] == 'D') {
				int tmp = qx[i];
				Union(bu[tmp], bv[tmp]);
			}
			else {
				Delete(qx[i]);
				head[qx[i]] = NEXT[head[qx[i]]];
                key[qx[i]] = v[head[qx[i]]]; //更改权值
				Insert(qx[i]);
			}
		}
		if (cnt == 0) cnt = 1;
		printf("Case %d: %.6lf
", cas, ans / cnt);
	}
}
原文地址:https://www.cnblogs.com/Anding-16/p/7153745.html