【树的重心维护 动态树】资源运输

上交的寝室没网实在是...据说acm时调试只有5分钟,基本上不用gdb,这点真是硬伤了,一般来说有难度的题目我要么不调试,要么调比较久,看来有必要锻炼静态调试。

题意:一开始有n个点,每次可以选择两个点连边,保证每次连边后是一个森林,对于每棵树来说,要选择一个点,树中其他点到它的距离和为此树的权值,要求每次操作后,整个森林中的树的权值和最小。

显然每棵树中要求的点是重心,因为考虑不是重心的时候,向重心移动,必定造成ans-n+2*size,如果不是重心,移动后ans会变小。

如果只是维护重心,其实简单一点,两颗树i,j相连(均以重心x,y为根),新重心必定在两重心的连线上,假设以y为根,对于x-y上的点z,如果它是重心,那么z子树以外的节点个数必定是<n/2的,也就是说z子树是>=n/2(边界有点麻烦,取不取等于自己考虑吧),当然只有这个条件不够,但是如果我们能找到最后一个子树大于n/2的,答案必定在附近的节点(汗),如此看来,我只要能维护子树大小,就可以直接找出重心了。

对于子树大小的维护,因为涉及到旋转换根这种操作,所以不能直接统计,但是我们对于每个节点统计虚边子树大小的话,这个权值是不会随着splay的形态改变而改变的,因此与splay子树大小一起,还是可以求出子树大小的。这样一来在splay上走一下应该可以在o(nlogn)的时间解决重心的维护问题。

可是题目还要求每个点到重心的距离和,这种东西与子树大小一样,是不能直接在splay上统计的,同样要用到虚边信息,麻烦的是,这个东西比子树大小的限制更多,如果不是在特殊的节点还不能随便求出,因此,考虑到两棵树相连顶多移动其中一棵树大小次,这里用启发式来做一下,每次再用动态树维护,当然,为了应付换根,还要处理一下,op记到头的答案,ed记到尾的答案,算法复杂度是o(nlognlogn)的,虽然题解中是o(nlogn)的,但是代码长至8kb,最后4个点没过,已经过了的点又比我跑得慢。

顺便提一下,用启发式就没有了二分时候的等于号取不取的麻烦问题...

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>		
const int oo=1073741819,maxn=50000;
using namespace std;
int l[maxn],r[maxn],rt[maxn],c[maxn][2],size[maxn],b[maxn],g[maxn],f[maxn],op[maxn],ed[maxn],sum[maxn],tot[maxn],sw[maxn],t[maxn];
int e,n,m,ans;
int find(int x) {if (b[x]!=x) b[x]=find(b[x]);return b[x];}
void swap(int x)
{
    e=l[x],l[x]=r[x],r[x]=e;
    e=op[x],op[x]=ed[x],ed[x]=e;
    e=c[x][0],c[x][0]=c[x][1],c[x][1]=e;
}
void updata(int x)
{
	sum[x]=sum[l[x]]+sum[r[x]]+f[x];
	size[x]=size[l[x]]+size[r[x]]+1;
	ed[x]=ed[l[x]]+(size[l[x]]+sum[l[x]])*(1+size[r[x]])+size[r[x]]*(1+f[x])+tot[x]+ed[r[x]];
	op[x]=op[l[x]]+size[l[x]]*(1+f[x])+tot[x]+(1+size[l[x]])*(size[r[x]]+sum[r[x]])+op[r[x]];
	c[x][0]=(c[l[x]][0]) ? c[l[x]][0] : x;
	c[x][1]=(c[r[x]][1]) ? c[r[x]][1] : x;
}
void pushdown(int x)
{
	if (sw[x]) {
      swap(l[x]),swap(r[x]);
    	sw[l[x]]^=1,sw[r[x]]^=1,sw[x]=0;
  }
	updata(x);
}
void left(int x)
{
	int y=rt[x],z=rt[y];
	r[y]=l[x],rt[l[x]]=y;updata(y);
	l[x]=y,rt[y]=x;
	if (l[z]==y) l[z]=x;else if (r[z]==y) r[z]=x;rt[x]=z;
}
void right(int x)
{
	int y=rt[x],z=rt[y];
	l[y]=r[x],rt[r[x]]=y,updata(y);
	r[x]=y,rt[y]=x;
	if (l[z]==y) l[z]=x;else if (r[z]==y) r[z]=x;rt[x]=z;
}
void splay(int x)
{
	pushdown(x);
	for (int y,z;(l[rt[x]]==x) || (r[rt[x]]==x);) {
		y=rt[x],z=rt[y];
		if ((l[z]==y) || (r[z]==y)) pushdown(z);pushdown(y);pushdown(x);
		if (l[y]==x) {
			if (l[z]==y) right(y);right(x);
		}
		else {
			if (r[z]==y) left(y);left(x);
		}
	}
	updata(x);
}
void access(int x)
{
	splay(x);
	f[x]+=sum[l[x]]+size[l[x]],tot[x]+=ed[l[x]]+sum[l[x]]+size[l[x]],l[x]=0; 
	updata(x);
	for (int y;rt[x];) {
		y=rt[x];
		splay(y);
		f[y]-=sum[x]+size[x]-sum[l[y]]-size[l[y]],tot[y]-=ed[x]+sum[x]+size[x]-ed[l[y]]-sum[l[y]]-size[l[y]],l[y]=x;
		updata(y);
    splay(x);
	}
}
int ask(int x,int &root,int n,int oroot)
{
	int ans=oo;root=0;
	for (;n;n--) {
		access(x),swap(x),sw[x]^=1;
		int tmp=ed[x];
		if (tmp<ans) ans=tmp,root=x;
		access(oroot),splay(x);
		if (!c[l[x]][1]) break;
		x=c[l[x]][1];
	}
	return ans;
}
int main()
{
	freopen("village.in","r",stdin);
	freopen("village.out","w",stdout);
		char ch;
		int x,y;
    scanf("%d%d\n",&n,&m);
    ans=0;
    for (int i=1;i<=n;i++) b[i]=i,g[i]=1,t[i]=0,size[i]=1,c[i][0]=c[i][1]=i;
    for (int i=1;i<=m;i++) {
        scanf("%c",&ch);
        if (i==7)
        	int k=1;
        if ('Q'==ch) {
            scanf("\n");
            printf("%d\n",ans);
        }
        else {
        		scanf("%d%d\n",&x,&y);
        		int ll=find(x),rr=find(y);
        		if (g[ll]>g[rr]) e=ll,ll=rr,rr=e,e=x,x=y,y=e;
        		ans-=t[ll]+t[rr];
        		b[ll]=rr,g[rr]+=g[ll];
        		int tmp=g[ll]; 
        		access(x);
        		for (ll=x;r[ll];ll=r[ll]) pushdown(ll);
    				swap(x),sw[x]^=1;
        		access(y);
        		rt[x]=y,f[y]+=size[x]+sum[x],tot[y]+=ed[x]+sum[x]+size[x],updata(y);
        		int root,tr;
        		for (tr=y;r[tr];tr=r[tr]) pushdown(tr);
        		t[rr]=ask(tr,root,tmp+1,ll);
        		access(root),swap(root),sw[root]^=1;
        		ans+=t[rr];
//        		printf("%d\n",ans);
        }
    }
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值