• [题解] 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);
    	}
    }
    
  • 相关阅读:
    仿百度输入框智能提示
    SQLSERVER2012 列存储索引的简单研究和测试
    Slider.js轻量级图片播放控件
    mina 字节数组编解码器的写法 II
    mongodb常用基础命令
    QT绘制半透明窗体(改写paintEvent,超级简单)
    在windows下的QT编程中的_TCHAR与QString之间的转换
    QT 遍历目录查找指定文件(比较简单)
    QNetworkAccessManager跳转URL处理(使用QNetworkRequest::RedirectionTargetAttribute获得跳转URL)
    跨平台生成GUID/UUID
  • 原文地址:https://www.cnblogs.com/Anding-16/p/7153745.html
Copyright © 2020-2023  润新知