BZOJ 3924: [Zjoi2015]幻想乡战略游戏 【点分树】

BZOJ传送门
洛谷传送门

题目分析:

要计算所有点到一个点的带权距离和。
先考虑如何计算:
每次找重心,构造出点分树,对于树中的每个节点维护三个信息:
s v [ i ] sv[i] sv[i]:以 i i i为根的子树的点权和
s d [ i ] sd[i] sd[i]:以 i i i为根的子树中每个点到 i i i的带权距离和
s f [ i ] sf[i] sf[i]:以 i i i为根的子树中每个点到 i i i(在点分树中)的父亲的带权距离和
查询点i,就可以从i往上走,统计答案。
修改 i i i点的点权,同样是从 i i i点往上走,维护祖先的三个值
维护的时候需要求点 i i i到点分树中的祖先在原树中的距离,可以用nlogn预处理RMQ,O(1)求LCA(然而事实证明树剖求LCA更快。。亲测BZOJ树剖快10s
因为点分树的树高是logn的,所以可以优雅地暴力往上爬

再考虑怎么找最优点。

  • 考虑决策点从 u u u转移到它的儿子 v v v会产生怎样的变化(增量),记 s u m [ i ] sum[i] sum[i]表示以 i i i为根的子树的点权和(原树中),记根为 r t rt rt
  • 减少的代价为 s u m [ v ] ∗ d i s ( u , v ) sum[v]*dis(u,v) sum[v]dis(u,v)
    增加的代价为 ( s u m [ r t ] − s u m [ v ] ) ∗ d i s ( u , v ) (sum[rt]-sum[v])*dis(u,v) (sum[rt]sum[v])dis(u,v)
  • 那么 v v v u u u优就表明 2 ∗ s u m [ v ] > s u m [ r t ] 2*sum[v]>sum[rt] 2sum[v]>sum[rt],可以看出这样的v最多只有一个,暴力(logn)计算u的儿子的答案,看是否存在比u优的,如果是就可以到v的点分树中继续找,否则就返回u的答案即可。

修改是 O ( l o g n ) O(logn) O(logn)的,查询 O ( l o g 2 n ) O(log^2n) O(log2n),总复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

打完3h过去了。。

Code:

#include
#include
#include
#define LL long long
#define maxn 100005
using namespace std;
inline void read(int &a){
    char c;bool f=0;
	while(!isdigit(c=getchar())) if(c=='-') f=1;
    for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
	if(f) a=-a;
}
int fir[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],tot,info[maxn],cnt;
inline void line(int x,int y,int z){nxt[++tot]=fir[x];fir[x]=tot;to[tot]=y;w[tot]=z;}
struct Edge{int u,v,o,nxt;}G[maxn];//o:origin path u->o
inline void Line(int x,int y,int o){G[++cnt]=(Edge){x,y,o,info[x]};info[x]=cnt;}
struct Origin_Tree{
	int dis[maxn],st[maxn<<2][20],pt,pos[maxn],lg[maxn<<2];
	void dfs(int u,int pre){
		st[++pt][0]=dis[u],pos[u]=pt;
		for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=pre)
			dis[v]=dis[u]+w[i],dfs(v,u),st[++pt][0]=dis[u];
	}
	inline int getdis(int x,int y){
		if(pos[x]>pos[y]) swap(x,y);
		int k=lg[pos[y]-pos[x]+1];
		return dis[x]+dis[y]-2*min(st[pos[x]][k],st[pos[y]-(1<<k)+1][k]);
	}
	void input(int n){
		for(int i=1,x,y,z;i<n;i++) read(x),read(y),read(z),line(x,y,z),line(y,x,z);
		dfs(1,0);
		lg[0]=-1;for(int i=1;i<(n<<2);i++) lg[i]=lg[i>>1]+1;
		for(int j=1;(1<<j)<=pt;j++)
			for(int i=1;i+(1<<j)-1<=pt;i++)
				st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	}
}T;
int n,m,Fa[maxn],siz[maxn],f[maxn];
bool vis[maxn];
int spt,rt;
LL sv[maxn],sd[maxn],sf[maxn];
void getroot(int u,int pre){
	siz[u]=1;f[u]=0;
	for(int i=fir[u],v;i;i=nxt[i]) if(!vis[v=to[i]]&&v!=pre){
		getroot(v,u),siz[u]+=siz[v];
		f[u]=max(f[u],siz[v]);
	}
	f[u]=max(f[u],spt-siz[u]);
	if(f[u]<f[rt]) rt=u;
}
void Build(int u,int tsz){
	vis[u]=1;
	for(int i=fir[u],v;i;i=nxt[i]) if(!vis[v=to[i]]){
		spt=(siz[v]<siz[u]?siz[v]:tsz-siz[u]),rt=0;//tsz!!
		getroot(v,0),Line(u,rt,v);
		Fa[rt]=u,Build(rt,siz[v]);
	}
}
inline void insert(int u,int val){
	sv[u]+=val;
	for(int i=u;Fa[i];i=Fa[i]){
		int dist=T.getdis(u,Fa[i]);//u -> Fa[i]!!
		sd[Fa[i]]+=1ll*val*dist;
		sf[i]+=1ll*val*dist;
		sv[Fa[i]]+=val;
	}
}
inline LL calc(int u){
	LL ret=sd[u];
	for(int i=u;Fa[i];i=Fa[i]) ret+=sd[Fa[i]]-sf[i]+(sv[Fa[i]]-sv[i])*T.getdis(Fa[i],u);
	return ret;
}
LL query(int u){
	LL tmp=calc(u);
	for(int i=info[u];i;i=G[i].nxt) if(calc(G[i].o)<tmp) return query(G[i].v);
	return tmp;
}
int main()
{
	read(n),read(m);
	T.input(n);
	spt=f[0]=n,rt=0;getroot(1,0);
	int root=rt,x,y;
	Build(root,n);
	while(m--){
		read(x),read(y),insert(x,y);
		printf("%lld\n",query(root));
	}
}

u p d : upd: upd:

  • 法二:
    同样是根据上面的结论,只不过不是暴力计算u的儿子的答案,可以线段树维护哪个儿子最优,变成修改log2,查询log,具体可以看这里

你可能感兴趣的:(树上问题,分治(二分),点分治)