UVA 1479 - Graph and Queries(Treap)

UVA 1479 - Graph and Queries

题目链接

题意:给定一个n个结点m条边的无向图,每个结点一个权值,现在有3种操作

D x,删除id为x的边

Q x k 计算与x结点的连通分量中第k大的数字,不存在就是0

C x v 把x结点权值改为v

要求计算所有Q操作的和除以Q操作的次数的值

思路:Treap的经典题,进行离线操作,把操作全部逆向进行,删边就可以转化为加边,就可以利用并查集,那么维护第k大就利用Treap

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <vector>
using namespace std;

typedef long long ll;

const int MAXN = 500005;
const int N = 20005;
const int M = 60005;
int cas = 0;

struct Treap {

    struct Node {

	Node *ch[2];
	int r, v, s;

	Node() {}
	Node(int v) {ch[0] = ch[1] = NULL; r = rand(); this->v = v; s = 1;}
	bool operator < (const Node& c) const {
	    return r < c.r;
	}
	int cmp(int x) const {
	    if (x == v) return -1;
	    return x < v ? 0 : 1;
	}
	void maintain() {
	    s = 1;
	    if (ch[0] != NULL) s += ch[0]->s;
	    if (ch[1] != NULL) s += ch[1]->s;
	}
    };

    //0 is left, 1 is right
    void rotate(Node* &o, int d) {
	Node *k = o->ch[d^1]; o->ch[d^1] = k->ch[d]; k->ch[d] = o;
	o->maintain(); k->maintain(); o = k;
    }

    void insert(Node* &o, int x) {
	if (o == NULL) o = new Node(x);
	else {
	    int d = (x < o->v ? 0 : 1);
	    insert(o->ch[d], x);
	    if (o->ch[d]->r > o->r) rotate(o, d^1);
	}
	o->maintain();
    }

    void remove(Node* &o, int x) {
	int d = o->cmp(x);
	if (d == -1) {
	    Node* u = o;
	    if (o->ch[0] != NULL && o->ch[1] != NULL) {
		int d = o->ch[0]->r > o->ch[1]->r ? 1 : 0;
		rotate(o, d); remove(o->ch[d], x);
	    } else {
		if (o->ch[0] == NULL) o = o->ch[1];
		else o = o->ch[0];
		delete u; u = NULL;
	    }

	} else
	    remove(o->ch[d], x);
	if (o != NULL) o->maintain();
    }

    bool isexist(Node *o, int x) {
	while (o != NULL) {
	    int d = o->cmp(x);
	    if (d == -1) return true;
	    else o = o->ch[d];
	}
	return false;
    }

    int findkth(Node *o, int k, int flag) { //1 is bigth, 0 is smallth
	if (o == NULL || k <= 0 || k > o->s) return 0;
	int s = (o->ch[flag] == NULL ? 0 : o->ch[flag]->s);
	if (k == s + 1) return o->v;
	else if (k <= s) return findkth(o->ch[flag], k, flag);
	else return findkth(o->ch[flag^1], k - s - 1, flag);
    }

    void mergeto(Node* &a, Node* &b) {//a mergeto b
	if (a->ch[0] != NULL) mergeto(a->ch[0], b);
	if (a->ch[1] != NULL) mergeto(a->ch[1], b);
	insert(b, a->v);
	delete a; a = NULL;
    }

    void removetree(Node* &x) {
	if (x == NULL) return;
	if (x->ch[0] != NULL) removetree(x->ch[0]);
	if (x->ch[1] != NULL) removetree(x->ch[1]);
	delete x; x = NULL;
    }

    int find(int x) {
	return parent[x] == x ? x : parent[x] = find(parent[x]);
    }

    Node* root[N];

    int n, m, query_num;
    int weight[N], parent[N], vis[M];
    ll query_tot;

    struct Query {
	int type, x, v;
	Query() {}
	Query(int type, int x, int v) {
	    this->type = type;
	    this->x = x;
	    this->v = v;
	}
    } Q[MAXN];

    int qn;

    struct Edge {
	int u, v;
	Edge() {}
	Edge(int u, int v) {
	    this->u = u;
	    this->v = v;
	}
	void read() {
	    scanf("%d%d", &u, &v);
	}
    } E[M];

    void add_edge(Edge x) {
	int pu = find(x.u), pv = find(x.v);
	if (pu != pv) {
	    if (root[pu]->s > root[pv]->s)
		swap(pu, pv);
	    parent[pu] = pv;
	    mergeto(root[pu], root[pv]);
	}
    }

    void init() {

	query_num = 0;
	query_tot = 0;

	for (int i = 1; i <= n; i++) {
	    scanf("%d", &weight[i]);
	    parent[i] = i;
	    removetree(root[i]);
	}
	for (int i = 1; i <= m; i++)
	    E[i].read();

	char C[10]; int a, b; qn = 0;
	memset(vis, 0, sizeof(vis));

	while (scanf("%s", C) && C[0] != 'E') {
	    if (C[0] == 'D') {
		scanf("%d", &a);
		vis[a] = 1;
		Q[qn++] = Query(0, a, 0);
	    }
	    else {
		scanf("%d%d", &a, &b);
		if (C[0] == 'C') {
		    int tmp = weight[a];
		    weight[a] = b;
		    b = tmp;
		    Q[qn++] = Query(1, a, b);
		}
		else Q[qn++] = Query(2, a, b);
	    }
	}

	for (int i = 1; i <= n; i++)
	    insert(root[i], weight[i]);
	for (int i = 1; i <= m; i++) {
	    if (vis[i]) continue;
	    add_edge(E[i]);
	}
    }

    void solve() {
	init();
	for (int i = qn - 1; i >= 0; i--) {
	    if (Q[i].type == 0)
		add_edge(E[Q[i].x]);
	    else if (Q[i].type == 1) {
		int pu = find(Q[i].x);
		remove(root[pu], weight[Q[i].x]);
		insert(root[pu], Q[i].v);
		weight[Q[i].x] = Q[i].v;
	    } else {
		int pu = find(Q[i].x);
		query_num++;
		query_tot += findkth(root[pu], Q[i].v, 1);
		//printf("%d %lld\n", query_num, query_tot);
	    }
	}
	printf("Case %d: %.6lf\n", ++cas, query_tot * 1.0 / query_num);
    }

} gao;

int main() {
    while (~scanf("%d%d", &gao.n, &gao.m) && gao.n || gao.m) {
	gao.solve();
    }
    return 0;
}
时间: 2024-12-26 15:27:21

UVA 1479 - Graph and Queries(Treap)的相关文章

uva 1479 - Graph and Queries(伸展树)

题目链接:uva 1479 - Graph and Queries 题目大意:有一张m条边的无向图,每个节点都有一个权值,现在有若干个操作, D x:删除ID为x的节点 Q x k:计算与节点x联通的节点当中,第k大的权值 C x v:把节点x的权值改为v 解题思路:把所有操作反过来处理,先执行所有的D操作,获得最终的图,然后逆操作的时候对于D来说即为合并操作,Q和C则是查询和修改操作. #include <cstdio> #include <cstring> #include &

UVA 1479 Graph and Queries (Treap)

题意:给一个无向图,再给一系列操作(以下3种),输出最后的平均查询结果. (1)D X 删除第x条边. (2)Q X k  查询与点X相连的连通分量中第k大的点的权值. (3)C X v  将点X的权值改为v. 吐槽:第一次写的人儿,WA,MLE,TLE各种惨.而且还好我写过splay,不然坑得更惨.耗时整整一天半在查错. 思路: 第一点,由于需要删除边,不是很方便,所以可以先将所有操作存起来,反序来操作,这样删边就变成加边,方便了不少.每次加边时若两点不属于同个连通分量,就将点少的整个连通分量

HDU 3726 Graph and Queries treap树

题目来源:HDU 3726 Graph and Queries 题意:见白书 思路:刚学treap 參考白皮书 #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; struct Node { Node *ch[2]; int r; int v; int s; Node(int v): v(v) { ch[0] = ch[1] = NULL; r = rand(); s

UVaLive 5031 Graph and Queries (Treap)

Graph and Queries 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 r

HDU 3726 Graph and Queries Treap

这是一个比较全面的题,涉及到了添加删除寻找第k大还有树的合并. 做法大概先执行所有的删边操作,建立最终的图,这里可以用并查集维护一下, 方便判断是不是在一个联通块中,然后对每个子块建立一个Treap,如果遇到添加边导致两个联通块合并成一个的情况,就将两棵树当中小的那个合并到大的那个里面.因为每次这样的合并操作,必然会有小的那个大小翻倍,其实复杂度是科学的,所以合并操作只要很裸很裸的一个一个节点插入就好. 这题有点考验代码能力,写起来比较的麻烦. #include <cstdio> #inclu

LA 5031 Graph and Queries —— Treap名次树

离线做法,逆序执行操作,那么原本的删除边的操作变为加入边的操作,用名次树维护每一个连通分量的名次,加边操作即是连通分量合并操作,每次将结点数小的子树向结点数大的子树合并,那么单次合并复杂度O(n1logn2),由于合并之后原本结点数少的子树结点数至少翻倍,所以每个结点最多被插入 logn 次,故总时间复杂度为 O(n log2n)  . 注意细节处理,代码如下: 1 #include <cstdio> 2 #include <cstdlib> 3 #include <cstr

hdu 3726 Graph and Queries(splay查询第k大,启发式合并,删除操作)

题目链接:hdu 3726 Graph and Queries 题意: 最开始给你n个点,每个点最开始有一个权值,并且都是独立的,现在给你m条边,表示对应的两个点是连接的. 现在有三种操作: Q x k,表示询问与x这个点联通的所有点中第k大的权值. D x,表示删除第x条边. C x y,表示改变x点的权值为y. 题解: 如果正着来做肯定比较麻烦,不好维护. 一看输出,可以离线处理,那么我们就倒着搞. 把所有的询问和边,点的变化全部存起来,然后用splay来维护. 每次删除就是倒着的合并,合并

uva 193 Graph Coloring(回溯)

uva 193 Graph Coloring You are to write a program that tries to find an optimal coloring for a given graph. Colors are applied to the nodes of the graph and the only available colors are black and white. The coloring of the graph is called optimal if

UVa 1479 (Treap 名次树) Graph and Queries

这题写起来真累.. 名次树就是多了一个附加信息记录以该节点为根的树的总结点的个数,由于BST的性质再根据这个附加信息,我们可以很容易找到这棵树中第k大的值是多少. 所以在这道题中用一棵名次树来维护一个连通分量. 由于图中添边比较方便,用并查集来表示连通分量就好了,但是删边不太容易实现. 所以,先把所有的边删去,然后逆序执行命令.当然,C命令也要发生一些变化,比如说顺序的情况是从a变成b,那么逆序执行的话应该就是从b变成a. 最后两棵树的合并就是启发式合并,把节点数少的数并到节点数多的数里去. 1