LA5031 Graph and Queries (Treap模版)

本文介绍了一道图论题目,涉及到Treap数据结构的应用。题目要求在无向图上进行删除边、查询节点在连通分量中的第K大权值以及改变节点权值的操作,并求最终查询结果的平均值。由于操作导致的树结构变化,作者采用了离线算法和启发式合并策略,保证了复杂度。文章中详细解释了Treap的原理及其在处理图操作中的应用,并提供了代码实现。
摘要由CSDN通过智能技术生成

题目:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=20332

题意:给定无向图,有三种操作,①删除第i条边②查询节点x所在的连通分量节点中第K大的权值③改变节点x的权值。现在问你最终的查询结果的平均值。

分析:查询第k大的值,用线段树可以,但是这里显然有很多连通分量,用线段树的话内存不够。这题是lrj白书上面的一题。由于第一次写Treap,还是仔细说一下。

Treap就是普通的Bst+Heap,Bst是为了有序,而Heap是为了让树平衡。

由于本题删边会导致树分裂,不好处理。可以用离线算法。

首先获得所有操作后图的最终状态。然后把操作顺序反过来。

删边就处理成加边,然后将集合和树合并,树合并要用启发式合并,就是规模小的树合并到大的树中去。这样对于在规模小的树中的节点而言,树的规模是成倍增长的,所以最多合并logn次,即所有的节点都最多移动logn次。

ps:初始化图的时候注意③操作。

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 1E9+9;
typedef pair <int,int> Pii;
typedef tuple<char,int,int> Tcii;
struct node          //定义Treap节点 
{
	node* son[2];     //左右子树 
	int v;   		  //值 
	int p;			  //优先级 
	int sz;           //以当前节点为根节点的树的大小 
	node(int x)
	{
		son[0]=son[1]=NULL;
		v=x;
		p=rand();
		sz=1;
	}
	void pushup()         //增加或删除节点需要修改子树的sz 
	{
		sz=1;
		if(son[0])	sz+=son[0]->sz;
		if(son[1])	sz+=son[1]->sz;
	}
};
inline int cmp(int a,int b)
{
	if(a==b)	return -1;
	return a>b?0:1;
}
inline void Rotate(node* &root,int d)    //d为0时左旋(右节点向上更新), d为1时右旋(左节点向上更新) 
{
	node* k = root->son[d^1];
	root->son[d^1] = k->son[d] ;
	k->son[d] = root;
	root->pushup();
	k->pushup();
	root = k ; 
}
void Insert(node* &root,int x)    
{
	if(NULL==root)
		root = new node(x);
	else
	{
		int d = root->v>x?0:1;        //这样写的话支持相同元素插入 
		Insert(root->son[d],x);
		if(root->p<root->son[d]->p)
			Rotate(root,d^1);
	}
	if(root)
		root->pushup();
}
void Erase(node* &root,int x)      
{
	int d = cmp(root->v,x);
	if(-1==d)
	{
		node* u = root;
		if(!root->son[0])
		{
			root = root->son[1];
			delete u;
		}
		else if(!root->son[1])
		{
			root = root->son[0];
			delete u;
		}
		else
		{
			int d2 = (root->son[0]->p>root->son[1]->p?1:0);
			Rotate(root,d2);
			Erase(root->son[d2],x);
		}
	}
	else
		Erase(root->son[d],x);
	if(root)
		root->pushup();
}
void Destroy(node* &root)
{
	if(root->son[0])
		Destroy(root->son[0]);
	if(root->son[1])
		Destroy(root->son[1]);
	delete root;
	root=NULL;
}
int Kth(node* &root,int k)  
{
	int lnum=0;
	if(root->son[0])
		lnum+=root->son[0]->sz;
	if(k==lnum+1)
		return root->v;
	if(k>lnum+1)
		return Kth(root->son[1],k-lnum-1);
	else
		return Kth(root->son[0],k);
}
void MoveTree(node* &root1,node* &root2)   //将以root1为根的树移到以root2为根的树上,同时销毁以root1为根的树 
{
	if(root1->son[0])
		MoveTree(root1->son[0],root2);
	if(root1->son[1])
		MoveTree(root1->son[1],root2);
	Insert(root2,root1->v);
	delete root1;
	root1=NULL;
}

const int N = 20000 + 666;
const int M = 60000 + 666;

int fa[N],w[N],nCase;
Pii edge[M];
int getOut[M],q;
Tcii Qu[555555]; 

node* RankTree[N];  //N棵名次树 

int FindSet(int cur)
{
	if(fa[cur]==cur)
		return cur;
	return fa[cur]=FindSet(fa[cur]);
}
void Merge(int u,int v)       // 合并集合,同时合并名次树 
{
	int s1=FindSet(u);
	int s2=FindSet(v);
	if(s1==s2)
		return ;
	if(RankTree[s1]->sz>RankTree[s2]->sz)
	{
		fa[s2]=s1;
		MoveTree(RankTree[s2],RankTree[s1]);
	}
	else
	{
		fa[s1]=s2;
		MoveTree(RankTree[s1],RankTree[s2]);
	}
}

void input(int n,int m)
{	
	fill(getOut,getOut+m+1,0);
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
	for(int i=1;i<=m;i++)
		scanf("%d%d",&edge[i].first,&edge[i].second);
	q=0;
	char op[2];
	while(scanf("%s",op),op[0]!='E')
	{
		int x=0,y=0;
		if(op[0]=='D')
		{
			scanf("%d",&x);
			Qu[q]=make_tuple('D',x,y);
			getOut[x]=1;
		}
		else if(op[0]=='Q')
		{
			scanf("%d%d",&x,&y);
			Qu[q]=make_tuple('Q',x,y);
		}
		else
		{
			scanf("%d%d",&x,&y);
			Qu[q]=make_tuple('C',x,w[x]);
			w[x]=y;
		}
		q++;
	}
}
void Init(int n,int m)
{
	for(int i=1;i<=n;i++)
	{
		if(RankTree[i])
			Destroy(RankTree[i]);
		RankTree[i] = new node(w[i]);
		fa[i]=i;	
	}
	for(int i=1;i<=m;i++) if(!getOut[i])
		Merge(edge[i].first,edge[i].second);
}
void solve(int n,int m)
{
	LL sum=0,times=0;
	for(int i=q-1;i>=0;i--)
	{
		char com = get<0>(Qu[i]);
		int x = get<1>(Qu[i]),y=get<2>(Qu[i]);
		if(com=='D')
			Merge(edge[x].first,edge[x].second);
		else if('Q'==com)
		{
			int s=FindSet(x);
			if(1<=y && y<=RankTree[s]->sz)
				sum+=Kth(RankTree[s],RankTree[s]->sz-y+1);
			times++;
		}
		else
		{
			int s=FindSet(x);
			Erase(RankTree[s],w[x]);
			Insert(RankTree[s],y);
			w[x]=y;
		}
	}
	printf("Case %d: %.6lf\n",++nCase,(double)sum/times);
}
int main()
{
	int n,m;
	while((scanf("%d%d",&n,&m)==2)&&n)
	{
		input(n,m);
		Init(n,m);
		solve(n,m);
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值