[zjoi2015]幻想乡战略游戏

前言

略略略

题目相关

链接

题目大意

给出一棵树,每次修改一个点的权值,维护一个带权重心
啥是带权重心?
设点 i i i的值为 V i V_i Vi我们要选一个点 u u u,每个点对应一个值:
∑ v = 1 n V v ∗ d i s ( u , v ) \sum_{v=1}^n V_v*dis(u,v) v=1nVvdis(u,v)
我们要选一个 u u u使得这个值最小
输出最小的值

数据范围

n , q ≤ 1 0 5 n,q\le10^5 n,q105,每个点的度数不多于20,时限6s

题解

动态点分治(点分树)
很久之前学了点分树,但一直都没做什么题
我们考虑这样一件事
假如我们现在选了一个点 u u u,那么只有它向带权重心的方向 v v v移动时答案才会变优(证明很显然,带权重心方向的点权和比另外一边的点权大,这样的话e*点权只差就是正的)
我们考虑最裸的情况,每次修改后,我们从根节点开始,如果其往某个子树移动答案会变优(设 s i z e i size_i sizei i i i的子树大小,那么这个条件是 e u , v ∗ ( s i z e u − s i z e v − s i z e v &lt; 0 e_{u,v}*(size_u-size_v-size_v&lt;0 eu,vsizeusizevsizev<0,容易发现这样的儿子对多只有一个),那么说明带权重心在那个子树中,那就把当前答案往那个子树移
我们发现这个复杂度可以被卡到 O ( n 2 ) \mathcal O(n^2) O(n2),因为往儿子走可能会走很多次

考虑点分治之后建出点分树,我们在跳子树的时候跳到那个子树的重心(注意与带权重心区分)即可,容易发现,由于点分树树高logn,所以这个跳子树的操作使得复杂度变为 O ( n l o g n ) \mathcal O(nlogn) O(nlogn)

现在要快速维护每个点的答案(当然是推过来而不是直接求)
我们维护每个分治子树权值和 s i z e u size_u sizeu u u u为分治重心),每个分治子树到重心的贡献值 g u g_u gu,每个节点子树到该子树的lca的父亲的贡献值 f u f_u fu
怎么维护呢?即对于一次修改怎么维护答案
我们发现,直接跳点分树即可,用 O ( 1 ) \mathcal O(1) O(1)求距离的话,单次修改的复杂度是 O ( l o g n ) \mathcal O(logn) O(logn)
再维护一个全局点权和 S I Z E SIZE SIZE
我们现在相当于对于当前答案的每条出边,并且询问这条边划分出的子树 s i z e size size是否大于全局的一半,如果是就走过去,直到找到答案点,最终计算答案
我们发现子树size和答案并不好维护
考虑记录历史走过的每一个点分重心,这样我们可以做到单次 l o g n logn logn的询问
我们发现一个点的度数总共只有 20 20 20个,直接枚举出边即可

综上,复杂度 O ( 20 n l o g 2 n ) \mathcal O(20nlog^2n) O(20nlog2n)
不是很满,但是复杂度感觉挺高的

代码

#include<cstdio>
#include<cctype>
#include<algorithm>
#include<vector>
namespace fast_IO
{
	const int IN_LEN=1000000,OUT_LEN=1000000;
	char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
	inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
	inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
	inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
#define rg register
typedef long long ll;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void Swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
	char cu=getchar();x=0;bool fla=0;
	while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
	while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
	if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
	if(x>=10)printe(x/10);
	putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
	if(x<0)putchar('-'),printe(-x);
	else printe(x);
}
const int maxn=100001,maxm=200001;
int n,Q;
int head[maxn],nxt[maxm],tow[maxm],vau[maxm],tmp;
inline void addb(const int u,const int v,const int w)
{
	tmp++;
	nxt[tmp]=head[u];
	head[u]=tmp;
	tow[tmp]=v;
	vau[tmp]=w;
}
int minn,root,SIZE,son[maxn];
bool vis[maxn];
void getroot(const int u,const int fa)
{
	son[u]=1;
	int maxx=0;
	for(rg int i=head[u];i;i=nxt[i])
	{
		const int v=tow[i];
		if(v!=fa&&!vis[v])
		{
			getroot(v,u);
			son[u]+=son[v];
			maxd(maxx,son[v]);
		}
	}
	maxd(maxx,SIZE-son[u]);
	if(maxx<minn)minn=maxx,root=u;
}
int ROOT,F[maxn];
std::vector<int>E[maxn];
int fr[maxn];
void solve(const int u,const int SZ,const int SON)
{
	vis[u]=1;
	for(rg int i=head[u];i;i=nxt[i])
	{
		const int v=tow[i];
		if(!vis[v])
		{
			minn=0x7fffffff,SIZE=son[v];
			if(SIZE>SON)SIZE=SZ-SON;
			getroot(v,u);
			E[u].push_back(root);
			F[root]=u,fr[root]=v;
			solve(root,SIZE,son[root]);
		}
	}
}
int rmq[maxm][21],top,fst[maxn];
ll dep[maxn];
void dfs(const int u,const int fa)
{
	rmq[++top][0]=u;
	fst[u]=top;
	for(rg int i=head[u];i;i=nxt[i])
	{
		const int v=tow[i];
		if(v!=fa)dep[v]=dep[u]+vau[i],dfs(v,u),rmq[++top][0]=u;
	}
}
int bit[21],log_[3000000];
int MIN(const int u,const int v){return dep[u]<dep[v]?u:v;}
int lca(const int u,const int v)
{
	const int A=min(fst[u],fst[v]),B=max(fst[u],fst[v]);
	return MIN(rmq[A][log_[B-A+1]],rmq[B-bit[log_[B-A+1]]+1][log_[B-A+1]]);
}
ll dis(const int u,const int v)
{
	return dep[u]+dep[v]-(dep[lca(u,v)]<<1);
}
ll g[maxn],f[maxn],size[maxn];
ll vvv[maxn];
int stack[21],tot;ll sz[21];
ll calc(const int l,const int r)
{
	ll res=0;
	for(rg int i=1;i<=tot;i++)
		if(dis(stack[i],l)>dis(stack[i],r))
			res+=sz[i];
	return res;
}
ll Res(const int p)
{
	ll res=g[p];
	stack[++tot]=p;
	for(rg int i=1;i<tot;i++)res+=g[stack[i]]-f[stack[i+1]]+(size[stack[i]]-size[stack[i+1]])*dis(stack[i],p);
	return res;
}
int main()
{
	bit[0]=1;
	for(rg int i=1;i<=20;i++)bit[i]=bit[i-1]<<1;
	for(rg int i=1;i<=20;i++)
		for(rg int j=bit[i];j<(bit[i]<<1);j++)
			log_[j]=i;
	read(n),read(Q);
	for(rg int i=1;i<n;i++)
	{
		int u,v,w;read(u),read(v),read(w);
		addb(u,v,w),addb(v,u,w);
	}
	minn=0x7fffffff,SIZE=n,getroot(1,1);
	ROOT=root;
	solve(root,SIZE,son[root]);
	dfs(1,1);
	for(rg int i=1;i<=20;i++)
		for(rg int j=1;j+bit[i]-1<=top;j++)
			rmq[j][i]=MIN(rmq[j][i-1],rmq[j+bit[i-1]][i-1]);
	SIZE=0;
	for(rg int i=1;i<=Q;i++)
	{
		int p,O,more;read(p),O=p,read(more),vvv[p]+=more;
		size[p]+=more,SIZE+=more;
		while(p!=ROOT)
		{
			const ll R=dis(O,F[p]);
			f[p]+=R*more,g[F[p]]+=R*more;
			p=F[p];
			size[p]+=more;
		}
		p=ROOT;
		tot=0;
		while(1)
		{
			bool fla=1;
			for(unsigned int j=0;j<E[p].size();j++)
			{
				const int v=E[p][j];
				const ll SZ=calc(p,fr[v])+size[v];
				if(SZ*2>SIZE)
				{
					fla=0;
					stack[++tot]=p,sz[tot]=size[p]-size[v];
					p=v;
					break;
				}
			}
			if(fla)break;
		}
		print(Res(p)),putchar('\n');
	}
	return flush(),0;
}

总结

对点分树的理解程度++
感觉自己还是做题太少了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值