P3345 [ZJOI2015] 幻想乡战略游戏

题目链接

神秘题,细节虽然不多但必须完全理解每个东西在做什么。

可以看代码,附带注释,这里讲一下大致的。

先随便钦定一个点为重心,发现如果原树重心不是它,则一定存在一个儿子,移动到它儿子更优,即 \(siz_x-siz_{son} -siz_{son}\) 为负数,就是 \(2\times siz_{son} > siz_x\),直接跑就好了。

题目又说度数 \(\le 20\),那么直接搞复杂度跟树高挂钩,单次为 \(20\times L\)

什么,跟树高挂钩?考虑树上常用的东西--点分树。

考虑点分树,点分树树高是 \(\log\) 的,然后处理一下就好了。

有人会说了:what,点分树树都跟原来的差了十万八千里了,还能做?

是的,还是可以的,考虑当前在点 \(i\),点分树上连了个点 \(j\),但实际上这颗子树在原树上与 \(i\) 连边的点是 \(z\),那只要 \(z\)\(i\) 优,那答案不就是在原树 \(z\) 的子树,点分树 \(j\) 的子树里吗(两个等价),那就可以转了。

注意啊,由 \(i\)\(j\) 时要给 \(z\) 挂上一个额外贡献,然后更新 \(z\)\(j\) 的值,之后在减去就好了。

还不懂的话,我想想,建议画图理解,如果点分树有问题建议先去看模版搞透了来。

代码有部分注释方便理解。

#include<bits/stdc++.h>
#define int long long
using namespace std;
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
	template<typename T>
	void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
	template<typename T,typename... Args>
	void read(T &_x,Args&...others){Read(_x);Read(others...);}
	const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
	#define pc(x) buf[plen++]=x
	#define flush(); fwrite(buf,1,plen,stdout),plen=0;
	template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 2e5+10;
int n,q,x,y,z,a[N],head[N],v[N],cnt,mx[N],siz[N],xn,fa[N],cnt1,dis[N];
int dfn[N],st[N][19],lg[N],fa1[N];//求lca 
int zx,mx1;//重心和最大值小的 
int D;//根节点 
vector<int>v1[N];//每个点对应的儿子(对于点分树上来讲)
vector<int>v2[N];//每个点对应的儿子(原树) 
int sum[N],sum1[N];//a_i的和考虑该子树到父亲的花费(若没父亲就设为到自己)
struct w
{
	int to,nxt,z;
}b[N<<1];
inline void add(int x,int y,int z)
{
	b[++cnt].nxt = head[x];
	b[cnt].to = y; b[cnt].z = z;
	head[x] = cnt;
}
inline int Min(int x,int y)//返回深度更小的,显然深度小的dfn也小,写dep也行 
{
	if(dfn[x] < dfn[y]) return x;
	return y;
}
inline int dist(int x,int y)//求两点距离,用的dfs序求lca,厉害吧 
{
	if(x == y) return 0;
	int X = x,Y = y;
	x = dfn[x],y = dfn[y];
	if(x > y) swap(x,y);
	x++; int k = lg[y-x+1];
	int o1 = Min(st[x][k],st[y-(1<<k)+1][k]);
	return dis[X]+dis[Y]-2*dis[o1];
}
void dfs(int x,int y)//找重心 
{
	siz[x] = 1,mx[x] = 0;
	for(int i = head[x];i;i = b[i].nxt)
		if(!v[b[i].to] && b[i].to != y)
			dfs(b[i].to,x),mx[x] = max(mx[x],siz[b[i].to]),siz[x] += siz[b[i].to];
	mx[x] = max(mx[x],xn-siz[x]);
	if(mx[x] < mx1) zx = x,mx1 = mx[x];
}
void dfs1(int x,int y)
{
	siz[x] = 1;
	for(int i = head[x];i;i = b[i].nxt)
		if(!v[b[i].to] && b[i].to != y)
			dfs1(b[i].to,x),siz[x] += siz[b[i].to];
}
void dfs2(int x,int y)
{
	siz[x] = 1; dfn[x] = ++cnt1; fa1[x] = y;
	for(int i = head[x];i;i = b[i].nxt)
		if(b[i].to != y)
			dis[b[i].to] = dis[x]+b[i].z,dfs2(b[i].to,x),siz[x] += siz[b[i].to];
}
inline void solve(int x)//构建点分树 
{
	v[x] = 1;
	for(int i = head[x];i;i = b[i].nxt)
		if(!v[b[i].to])
		{
			mx1 = 1e9; xn = siz[b[i].to]; dfs(b[i].to,x);
			v1[x].push_back(zx); v2[x].push_back(b[i].to);
			fa[zx] = x; dfs1(zx,0); solve(zx);
		}
}
void change(int x,int y)//加入 
{
	a[x] += y;
	for(int i = x;i;i = fa[i])
	{
		sum[i] += y;
		if(fa[i]) sum1[i] += y*dist(x,fa[i]);
		else sum1[i] += y*dist(x,i); 
	}
}
int query(int x)
{
	int ans = 0,S = 0,S1 = sum[x];
	for(int i = 0;i < v1[x].size();i++) S += sum1[v1[x][i]];
	for(int i = 0;i < v1[x].size();i++)
		if(sum[v1[x][i]]*2 > sum[x])//转移过去更优 
		{
			int o = S1-sum[v1[x][i]];
			ans = S-sum1[v1[x][i]]+(S1-sum[v1[x][i]])*dist(x,v2[x][i]);//这些到v2_{x,i}的距离 
			a[v2[x][i]] += o;
			for(int j = v2[x][i];j != x;j = fa[j])
			{
				sum[j] += o; 
				if(fa[j]) sum1[j] += o*dist(v2[x][i],fa[j]);
				else sum1[j] += o*dist(v2[x][i],j);
			}//将这些挂在v2_{x,i}上面,然后继续跑,之后删掉就好了 
			ans += query(v1[x][i]);
			for(int j = v2[x][i];j != x;j = fa[j])
			{
				sum[j] -= o; 
				if(fa[j]) sum1[j] -= o*dist(v2[x][i],fa[j]);
				else sum1[j] -= o*dist(v2[x][i],j);
			}
			a[v2[x][i]] -= o;
			return ans;//显然只会存在一个 
		}
	return S;//没有就返回子树到它的值 
}
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	read(n),read(q);
	for(int i = 1;i < n;i++) read(x),read(y),read(z),add(x,y,z),add(y,x,z);
	mx1 = 1e9,xn = n; dfs(1,0); D = zx;
	dfs2(zx,0);
	solve(zx);
	for(int i = 2;i <= n;i++) lg[i] = lg[i/2]+1;
	for(int i = 1;i <= n;i++) st[dfn[i]][0] = fa1[i];//注意dfs序初值是这样赋的 
	for(int i = 1;i <= lg[n];i++)
		for(int j = 1;j+(1<<i)-1 <= n;j++)
			st[j][i] = Min(st[j][i-1],st[j+(1<<(i-1))][i-1]); 
	while(q--)
	{
		read(x),read(y);//a[x]+=y 
		change(x,y);
		print(query(D)),pc('\n');//D是根节点 
	}
	flush();
	return 0;
}
/*
警告,滥用本题者将被封号。这么牛的题,更要做了 
考虑dp,度数<=20,暴力找y,sum_y*2 > sum_x 那么答案就在y子树内(x到y会加上sum_x-sum_y - sum_y 的贡献)
所以此题跟树高有关系,考虑点分树,树log树高
维护一个sum_x表示a_i和,sum1_x表示每个x子树内的点z到fa_x的dis(z,fa_x)*a_z总和
用dfs序求lca,关键结论:父亲dfn比儿子dfn小 
*/
posted @ 2025-05-03 23:22  kkxacj  阅读(25)  评论(0)    收藏  举报