zoukankan      html  css  js  c++  java
  • [ZJOI2015]幻想乡战略游戏

    VIII.[ZJOI2015]幻想乡战略游戏

    题意:求一个树的带权重心,带修改。

    现在首位的题解的方法太恶心了,这里介绍我自己的理解。

    假设重心为\(x\),我们有它的代价为:

    \[\sum\limits_{i=1}^{n}\operatorname{dis}(i,x)\times val_i \]

    其中\(val_i\)表示\(i\)节点的权值。

    那如果将重心向\(x\)的某一个相邻节点\(y\)移动一格的话,

    \(\operatorname{len}(x,y)\)为连接\(x,y\)的边的权值,

    则对于所有\(y\)一侧的节点,距离减少了\(\operatorname{len}(x,y)\);对于所有\(x\)一侧的节点,这反而增加了\(\operatorname{len}(x,y)\)

    我们现在假设\(x\)为根。\(sum_i\)表示\(i\)节点的子树和,

    \(y\)一侧节点的贡献为\(sum_y\times\operatorname{len}(x,y)\)

    \(x\)一侧节点的贡献为\(-(sum_x-sum_y)\times\operatorname{len}(x,y)\)

    两个东西加一起,整理整理,我们得到可以移动的充分必要条件为:

    \[2\times sum_y>sum_x \]

    显然,对于每个\(x\),这样的\(y\)唯一。

    如果我们在原树上这么搞的话,显然会TLE(想想看如果是条链的话)。我们考虑到点分树上处理。

    我们这次令\(sum_i\)表示点分树上子树和,而\(dssum_i\)表示:

    如果\(i\)有父亲(即不是点分树的根),则为\(\sum\limits_{j\in subtree_i}val_j\times\operatorname{dis}(j,fa_i)\)。它的意义是到父亲的加权距离和。

    否则,即它是点分树的根,则为\(\sum\limits_{j\in subtree_i}val_j\times\operatorname{dis}(j,i)\)。它的意义是到自己的加权距离和。

    我们考虑当前在节点\(x\)。假设我们发现了一个子节点\(y\)符合\(2\times sum_y>sum_x\),然后怎么办呢?

    你可能会想着直接跳过去,但抱歉,点分树上的子树和不是真实的子树和,也就是说,这个\(sum_y\)不一定是真实的\(y\)侧节点贡献。

    那怎么办呢?

    我们考虑当\(x\)为点分树树根时,显然,这里的\(sum_y\)一定是真实的子树和。因此我们这里的\(y\)是可信的。

    然后,这就是比较惊奇的地方了:

    设一个\(z\),它是\(y\)侧节点,同时直接与\(x\)相连。换句话说,\(z\)是原树上\(x\)\(y\)侧的儿子

    我们\(y\)子树外侧的所有东西,看作一个点,直接连到\(z\)

    换句话说,当\(y\)为根的时候,它有许多儿子;但其中,\(x\)方向的那个是可以忽略的,因为\(y\)就是从\(x\)过来的,肯定不会再回去了;所以我们就把它连到\(z\)上,当作\(z\)的儿子。

    可以参考LCT的虚儿子这一概念。

    我们令一个\(imag\)数组表示虚儿子的\(dssum\)的和。然后,我们可以写出这样的求值部分:

    ll ask(int x){
    	for(auto i:v[x]){//v is a vector<pair<int,int> >,which stores x's sons in the cdt and the 'z's they are corresponded to
    		int y=i.first,z=i.second;
    		if((sum[y]<<1)<=sum[x])continue;//judge if y is ok
    		ll cnt=val[x],out=imag[x];//cnt stands for the sum of val in 'x side', while out stands for all the nodes in 'x side', the sum of [their (distances to x) times (their val)]
    		for(auto u:v[x])if(u.first!=y)out+=dssum[u.first],cnt+=sum[u.first];//calculate cnt and out from the sons of x which are different from y
    		val[z]+=cnt;//the sum of val in the imagined subtree of z can be directly added onto val_z
    		ll olddssum=dssum[y];//store the original dssum_y for restore it later
    		fa[y]=0;//cut the edge between y and x, make y the root
    		dssum[y]=imag[y];//the values of the imagined sons
    		for(auto u:v[y])dssum[y]+=dssum[u.first];//the values of the real sons
    		imag[z]+=cnt*DIS(z,x)+out;//add (the value from x) to z
    		for(int u=z;u;u=fa[u])sum[u]+=cnt,dssum[u]+=cnt*(fa[u]?DIS(fa[u],x):DIS(u,x))+out;//the whole chain from x to z shall be updated
    		ll res=ask(y);
    		for(int u=z;u;u=fa[u])sum[u]-=cnt,dssum[u]-=cnt*(fa[u]?DIS(fa[u],x):DIS(u,x))+out;//restore the chain from z to z
    		imag[z]-=cnt*DIS(z,x)+out;//restore
    		fa[y]=x;//restore
    		dssum[y]=olddssum;//restore
    		val[z]-=cnt;//restore
    		return res;
    	}
    	return dssum[x];//if there's no proper son, then the answer is x itself
    }
    

    则这个东西的复杂度是\(O\Big(n\log n\times\max(\text{儿子数量},\log n)\Big)\),因为找\(y\)时要统计所有儿子。但是题目最下面有一行:

    非常神奇的是,对于所有数据,这棵树上的点的度数都不超过\(20\)

    点分树上儿子数量是不大于原树上儿子数量的,因此复杂度是正确的。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,m,dep[100100],mn[200100][20],in[100100],LG[200100],tot,fa[100100],fr[100100],admin;
    namespace Tree{
    	int sz[100100],SZ,msz[100100],ROOT,head[100100],cnt;
    	struct node{
    		int to,next,val;
    	}edge[200100];
    	void ae(int u,int v,int w){
    		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
    		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
    	}
    	bool vis[100100];
    	void getsz(int x,int fa){
    		sz[x]=1;
    		for(int i=head[x];i!=-1;i=edge[i].next)if(!vis[edge[i].to]&&edge[i].to!=fa)getsz(edge[i].to,x),sz[x]+=sz[edge[i].to];
    	}
    	void getroot(int x,int fa){
    		sz[x]=1,msz[x]=0;
    		for(int i=head[x];i!=-1;i=edge[i].next)if(!vis[edge[i].to]&&edge[i].to!=fa)getroot(edge[i].to,x),sz[x]+=sz[edge[i].to],msz[x]=max(msz[x],sz[edge[i].to]);
    		msz[x]=max(msz[x],SZ-sz[x]);
    		if(msz[x]<msz[ROOT])ROOT=x;
    	}
    	void solve(int x){
    		getsz(x,0); 
    		vis[x]=true;
    		for(int i=head[x];i!=-1;i=edge[i].next)if(!vis[edge[i].to])ROOT=0,SZ=sz[edge[i].to],getroot(edge[i].to,0),fa[ROOT]=x,fr[ROOT]=edge[i].to,solve(ROOT);
    	}
    	void getural(int x,int fa){
    		mn[++tot][0]=x,in[x]=tot;
    		for(int i=head[x];i!=-1;i=edge[i].next)if(edge[i].to!=fa)dep[edge[i].to]=dep[x]+edge[i].val,getural(edge[i].to,x),mn[++tot][0]=x;
    	}
    }
    int MIN(int i,int j){
    	return dep[i]<dep[j]?i:j;
    }
    int LCA(int i,int j){
    	i=in[i],j=in[j];
    	if(i>j)swap(i,j);
    	int k=LG[j-i+1];
    	return MIN(mn[i][k],mn[j-(1<<k)+1][k]);
    }
    int DIS(int i,int j){
    	return dep[i]+dep[j]-dep[LCA(i,j)]*2;
    }
    namespace cdt{
    	vector<pair<int,int> >v[100100];
    	int val[100100];
    	ll sum[100100],dssum[100100],imag[100100];
    	void init(){
    		for(int i=1;i<=n;i++)if(fa[i])v[fa[i]].push_back(make_pair(i,fr[i]));
    	}
    	void modify(int x,int y){
    		val[x]+=y;
    		for(int u=x;u;u=fa[u]){
    			sum[u]+=y;
    			dssum[u]+=1ll*y*(fa[u]?DIS(fa[u],x):DIS(u,x));
    		}
    	}
    	ll ask(int x){
    		for(auto i:v[x]){
    			int y=i.first,z=i.second;
    			if((sum[y]<<1)<=sum[x])continue;//judge if y is ok
    			ll cnt=val[x],out=imag[x];//cnt stands for the sum of val in 'x side', while out stands for all the nodes in 'x side', the sum of [their (distances to x) times (their val)]
    			for(auto u:v[x])if(u.first!=y)out+=dssum[u.first],cnt+=sum[u.first];//calculate cnt and out from the sons of x which are different from y
    			val[z]+=cnt;//the sum of val in the imagined subtree of z can be directly added onto val_z
    			ll olddssum=dssum[y];//store the original dssum_y for restore it later
    			fa[y]=0;//cut the edge between y and x, make y the root
    			dssum[y]=imag[y];//the values of the imagined sons
    			for(auto u:v[y])dssum[y]+=dssum[u.first];//the values of the real sons
    			imag[z]+=cnt*DIS(z,x)+out;//add (the value from x) to z
    			for(int u=z;u;u=fa[u])sum[u]+=cnt,dssum[u]+=cnt*(fa[u]?DIS(fa[u],x):DIS(u,x))+out;//the whole chain from x to z shall be updated
    			ll res=ask(y);
    			for(int u=z;u;u=fa[u])sum[u]-=cnt,dssum[u]-=cnt*(fa[u]?DIS(fa[u],x):DIS(u,x))+out;//restore the chain from z to z
    			imag[z]-=cnt*DIS(z,x)+out;//restore
    			fa[y]=x;//restore
    			dssum[y]=olddssum;//restore
    			val[z]-=cnt;//restore
    			return res;
    		}
    		return dssum[x];//if there's no proper son, then the answer is x itself
    	}
    }
    int main(){
    	scanf("%d%d",&n,&m),memset(Tree::head,-1,sizeof(Tree::head));
    	for(int i=1,x,y,z;i<n;i++)scanf("%d%d%d",&x,&y,&z),Tree::ae(x,y,z);
    	Tree::msz[0]=n+1,Tree::SZ=n,Tree::getroot(1,0),admin=Tree::ROOT,Tree::solve(Tree::ROOT);
    	Tree::getural(1,0);
    	for(int i=2;i<=tot;i++)LG[i]=LG[i>>1]+1;
    	for(int j=1;j<=LG[tot];j++)for(int i=1;i+(1<<j)-1<=tot;i++)mn[i][j]=MIN(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
    //	for(int i=1;i<=n;i++)if(fa[i])printf("%d %d\n",fa[i],i);
    	cdt::init();
    	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),cdt::modify(x,y),printf("%lld\n",cdt::ask(admin));
    	return 0;
    } 
    

  • 相关阅读:
    PPT文档页数显示的增加和更新
    Android http超时选项的测试
    64位win7硬盘安装64位ubuntu 13.04
    为博客园添加目录的配置总结
    Android webview通过http get下载文件下载两次的问题及解决方法
    ubuntu12.04安装搜狗输入法和配置
    samsung Galaxy s2(GT i9100g )刷机升级至4.4小记
    uva 11584 Partitioning by Palindromes
    uva 10534 Wavio Sequence
    poj 1185 炮兵阵地
  • 原文地址:https://www.cnblogs.com/Troverld/p/14605809.html
Copyright © 2011-2022 走看看