[学习笔记]动态点分治

动态点分治就这么写了一天。。。

首先,动态点分治跟普通点分治几乎一模一样。所以点分治不会的可以右转模板区了。

我们发现处理树上路径不好处理,所以我们考虑点分治。但点分治带修改呢?

那动态点分治啊!

动态点分治就是建出原树的点分树,然后在点分树上暴力跳。因为点分树的高度是 \(\log n\) 的,所以一般我们会再套一个 \(\log n\) 的数据结构。

建出点分树:

void getrt(int x,int f){
	siz[x]=1;maxp[x]=0;
	for(int i=head[x],y;i;i=nxt[i]){
		y=to[i];
		if(y==f||vis[y]) continue;
		getrt(y,x);siz[x]+=siz[y];
		maxp[x]=max(maxp[x],siz[y]);
	}
	maxp[x]=max(maxp[x],sum-siz[x]);
	if(maxp[x]<maxp[rt]) rt=x;
}

void solve(int x,int f,int pre){
	fa[x]=f;vis[x]=1;
	for(int i=head[x],y;i;i=nxt[i]){
		y=to[i];
		if(vis[y]) continue;
		rt=0;sum=(siz[x]>siz[y])?siz[y]:pre-siz[x];
		getrt(y,x);solve(rt,x,siz[x]);
	}
}

我们发现其实就多了一句 \(fa[x]=f\)

题目:

【BZOJ1095】【ZJOI2007】捉迷藏

【Luogu3345】【ZJOI2015】幻想乡战略游戏

【BZOJ3730】震波

【BZOJ4372】烁烁的游戏

还没做完。我们以捉迷藏为例吧。

我们发现查询最远的两个点的距离其实就是取深度最大和次大的两个黑点。所以我们建出点分树,然后暴力上跳修改。

因为对于点分树的一条链都要修改,所以我们需要一个可以查询最大值和次大值,带删除的数据结构。

那肯定是双堆啊!

struct Heap{
	priority_queue<int> a,b;
	void push(int x){a.push(x);}
	void pop(int x){b.push(x);}
	int size(){return a.size()-b.size();}
	int top(){
		while(!b.empty()&&a.top()==b.top()) a.pop(),b.pop();
		return a.top();
	}
	int calc(){
		if(size()<2) return 0;
		int fir=top();pop(fir);
		int sec=top();push(fir);
		return fir+sec;
	}
};

我们发现时空复杂度都是 \(O(n\log^2 n)\)

因为做不到 \(O(n\log n)\),所以查询树上两点路径我选择了倍增。

不开 \(O_2\) 还过不去。。。常数贼大

\(Code\ Below:\)

#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
const int inf=0x3f3f3f3f;
int n,m,a[maxn],dep[maxn],f[maxn][18],cnt;
int fa[maxn],maxp[maxn],siz[maxn],vis[maxn],rt,sum;
int head[maxn],to[maxn<<1],nxt[maxn<<1],tot;

struct Heap{
	priority_queue<int> a,b;
	void push(int x){a.push(x);}
	void pop(int x){b.push(x);}
	int size(){return a.size()-b.size();}
	int top(){
		while(!b.empty()&&a.top()==b.top()) a.pop(),b.pop();
		return a.top();
	}
	int calc(){
		if(size()<2) return 0;
		int fir=top();pop(fir);
		int sec=top();push(fir);
		return fir+sec;
	}
}A[maxn],B[maxn],C;

inline int read(){
	register int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return (f==1)?x:-x;
}

inline void addedge(int x,int y){
	to[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}

void dfs(int x,int fa){
	f[x][0]=fa;dep[x]=dep[fa]+1;
	for(int i=1;i<=17;i++) f[x][i]=f[f[x][i-1]][i-1];
	for(int i=head[x],y;i;i=nxt[i]){
		y=to[i];
		if(y==fa) continue;
		dfs(y,x);
	}
}

int LCA(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=17;i>=0;i--)
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	if(x==y) return x;
	for(int i=17;i>=0;i--)
		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}

int getdis(int x,int y){
	return dep[x]+dep[y]-2*dep[LCA(x,y)];
}

void getrt(int x,int f){
	siz[x]=1;maxp[x]=0;
	for(int i=head[x],y;i;i=nxt[i]){
		y=to[i];
		if(y==f||vis[y]) continue;
		getrt(y,x);siz[x]+=siz[y];
		maxp[x]=max(maxp[x],siz[y]);
	}
	maxp[x]=max(maxp[x],sum-siz[x]);
	if(maxp[x]<maxp[rt]) rt=x;
}

void solve(int x,int f,int pre){
	fa[x]=f;vis[x]=1;
	for(int i=head[x],y;i;i=nxt[i]){
		y=to[i];
		if(vis[y]) continue;
		rt=0;sum=(siz[x]>siz[y])?siz[y]:pre-siz[x];
		getrt(y,x);solve(rt,x,siz[x]);
	}
}

void turn_off(int u){
	B[u].push(0);
	if(B[u].size()==2) C.push(B[u].calc());
	int x,y,dis,lastdis,lastmax,lastsiz,nowmax,nowsiz;
	for(x=u;fa[x];x=fa[x]){
		y=fa[x];dis=getdis(u,y);
		lastdis=A[x].top();A[x].push(dis);
		if(dis<=lastdis) continue;
		lastmax=B[y].calc();lastsiz=B[y].size();
		if(lastdis) B[y].pop(lastdis);
		B[y].push(dis);
		nowmax=B[y].calc();nowsiz=B[y].size();
		if(nowmax>lastmax){
			if(lastsiz>=2) C.pop(lastmax);
			if(nowsiz>=2) C.push(nowmax);
		}
	}
}

void turn_on(int u){
	if(B[u].size()==2) C.pop(B[u].calc());
	B[u].pop(0);
	int x,y,dis,lastdis,lastmax,lastsiz,nowmax,nowsiz;
	for(x=u;fa[x];x=fa[x]){
		y=fa[x];dis=getdis(u,y);
		lastdis=A[x].top();A[x].pop(dis);
		if(dis<lastdis) continue;
		lastmax=B[y].calc();lastsiz=B[y].size();
		B[y].pop(lastdis);
		if(A[x].top()) B[y].push(A[x].top());
		nowmax=B[y].calc();nowsiz=B[y].size();
		if(nowmax<lastmax){
			if(lastsiz>=2) C.pop(lastmax);
			if(nowsiz>=2) C.push(nowmax);
		}
	}
}

int main()
{
	n=read();
	int x,y;char op;
	for(int i=1;i<n;i++){
		x=read(),y=read();
		addedge(x,y);addedge(y,x);
	}
	dfs(1,0);
	maxp[0]=inf;sum=n;
	getrt(1,0);solve(rt,0,n);
	for(int i=1;i<=n;i++) A[i].push(0);
	for(int i=1;i<=n;i++) cnt++,turn_off(i);
	m=read();
	while(m--){
		op=getchar();
		while(!isalpha(op)) op=getchar();
		if(op=='C'){
			x=read();
			if(a[x]==0) cnt--,turn_on(x);
			else cnt++,turn_off(x);
			a[x]^=1;
		}
		else {
			if(cnt==0) printf("-1\n");
			else if(cnt==1) printf("0\n");
			else printf("%d\n",C.top());
		}
	}
	return 0;
}

还有一道简单点的,\([ZJOI2015]\) 幻想乡战略游戏。

我们用 \(sumd[x]\) 表示 \(x\) 结点在点分树上的子树和,\(dis1[x]\) 表示 \(x\) 结点在点分树上的子树 \(\sum_{y\in x}d_y\times dis(x,y)\)\(dis2[x]\) 表示 \(x\) 结点在点分树上的子树 \(\sum_{y\in x}d_y\times dis(fa_x,y)\)

在点分树上暴力跳不难,主要是怎么求出答案。

显然带权重心只有一个,所以我们暴力找重心。若用 \(RMQ\)\(LCA\),时间复杂度 \(O(n\log n)\)

我先偷懒写了一个倍增,两个 \(log\) 就直接 \(T\) 掉了。。。后来改成 \(RMQ\) 才过

\(Code\ Below:\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100000+10;
const int inf=0x3f3f3f3f;
int n,m,a[maxn],dep[maxn],lg[maxn<<1],st[maxn],mp[maxn<<1],tim;
int fa[maxn],maxp[maxn],siz[maxn],vis[maxn],rt,sum;
int head[maxn],to[maxn<<1],nxt[maxn<<1],val[maxn<<1],tot;
ll Min[maxn<<1][18],dis[maxn],dis1[maxn],dis2[maxn],sumv[maxn];
vector<pair<int,int> > G[maxn];

inline int read(){
    register int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return (f==1)?x:-x;
}

inline void addedge(int x,int y,int w){
    to[++tot]=y;
    val[tot]=w;
    nxt[tot]=head[x];
    head[x]=tot;
}

void dfs(int x,int fa){
    dep[x]=dep[fa]+1;
	st[x]=++tim;mp[tim]=x;
    for(int i=head[x],y;i;i=nxt[i]){
        y=to[i];
        if(y==fa) continue;
        dis[y]=dis[x]+val[i];
        dfs(y,x);mp[++tim]=x;
    }
}

int LCA(int x,int y){
    int l=st[x],r=st[y];
    if(l>r) swap(l,r);
	int k=lg[r-l+1];
    return (dep[Min[l][k]]<dep[Min[r-(1<<k)+1][k]])?Min[l][k]:Min[r-(1<<k)+1][k];
}

ll getdis(int x,int y){
    return dis[x]+dis[y]-2*dis[LCA(x,y)];
}

void getrt(int x,int f){
    siz[x]=1;maxp[x]=0;
    for(int i=head[x],y;i;i=nxt[i]){
        y=to[i];
        if(y==f||vis[y]) continue;
        getrt(y,x);siz[x]+=siz[y];
        maxp[x]=max(maxp[x],siz[y]);
    }
    maxp[x]=max(maxp[x],sum-siz[x]);
    if(maxp[x]<maxp[rt]) rt=x;
}

void solve(int x,int f,int pre){
    fa[x]=f;vis[x]=1;
    for(int i=head[x],y;i;i=nxt[i]){
        y=to[i];
        if(vis[y]) continue;
        rt=0;sum=(siz[x]>siz[y])?siz[y]:pre-siz[x];
        getrt(y,x);G[x].push_back(make_pair(rt,y));solve(rt,x,siz[x]);
    }
}

void update(int u,int v){
	sumv[u]+=v;
	int x,y;ll dis;
	for(x=u;fa[x];x=fa[x]){
		y=fa[x];dis=getdis(u,y)*v;
		dis1[y]+=dis;dis2[x]+=dis;sumv[y]+=v;
	}
}

ll calc(int u){
	int x,y;ll ans=dis1[u],dis;
	for(x=u;fa[x];x=fa[x]){
		y=fa[x];dis=getdis(u,y);
		ans+=dis1[y]-dis2[x];
		ans+=dis*(sumv[y]-sumv[x]);
	}
	return ans;
}

ll query(int x){
	ll ans=calc(x),tmp;
	for(int i=0;i<G[x].size();i++){
		tmp=calc(G[x][i].second);
		if(tmp<ans) return query(G[x][i].first);
	}
	return ans;
}

int main()
{
    n=read(),m=read();
    int x,y,w,root;
    for(int i=1;i<n;i++){
        x=read(),y=read(),w=read();
        addedge(x,y,w);addedge(y,x,w);
    }
    dfs(1,0);
    maxp[0]=inf;sum=n;
    getrt(1,0);root=rt;solve(rt,0,n);
    for(int i=2;i<=tim;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=tim;i++) Min[i][0]=mp[i];
    for(int j=1;j<=17;j++)
    	for(int i=1;i+(1<<j)-1<=tim;i++) Min[i][j]=(dep[Min[i][j-1]]<dep[Min[i+(1<<(j-1))][j-1]])?Min[i][j-1]:Min[i+(1<<(j-1))][j-1];
    while(m--){
    	x=read(),y=read();update(x,y);
    	printf("%lld\n",query(root));
	}
    return 0;
}

再放一道我自己想出来的好了,\(BZOJ3730\) 震波

一看觉得佷裸有木有,但是一算空间复杂度 \(O(n\log^2 n)\),开始慌了。。。

不过我们要相信两个 \(\log\) 跑不满!上次同样是两个 \(\log\) 我开了 \(100\) 倍就过了。。。

我们记录 \(2n\) 棵权值线段树,\(1\sim n\) 中第 \(i\) 棵线段树记录 \(i\) 在点分树上的子树所有结点到 \(fa_i\) 以距离为下标的权值和,\(n+1\sim 2n\) 中第 \(i+n\) 棵线段树记录 \(i\) 在点分树上的子树所有结点到 \(i\) 以距离为下标的权值和,然后每次暴力上跳再容斥一下就好了。分享一下手贱经历:

int ask(int u,int k){
	int x,y,dis,ans=query(T[u+n],0,k,0,n);
	for(x=u;fa[x];x=fa[x]){
		y=fa[x];dis=LCA::Dis(u,y);
		if(dis>k) break;
		ans+=query(T[y+n],0,k-dis,0,n);
		ans-=query(T[x],0,k-dis,0,n);
	}
	return ans;
}

应该改成:

int ask(int u,int k){
	int x,y,dis,ans=query(T[u+n],0,k,0,n);
	for(x=u;fa[x];x=fa[x]){
		y=fa[x];dis=LCA::Dis(u,y);
		if(dis>k) continue;
		ans+=query(T[y+n],0,k-dis,0,n);
		ans-=query(T[x],0,k-dis,0,n);
	}
	return ans;
}

然后我把 \(RMQ\) 换成树剖,线段树封装起来,快了 \(4000ms\)。。。

连续内存访问的速度就是吼啊!

\(Code\ Below:\)

#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
const int inf=0x3f3f3f3f;
int n,m,a[maxn],dep[maxn],lg[maxn<<1],Min[maxn<<1][19],st[maxn],mp[maxn<<1],tim;
int fa[maxn],maxp[maxn],siz[maxn],vis[maxn],rt,Sum;
int head[maxn],to[maxn<<1],nxt[maxn<<1],tot;
int T[maxn<<1],L[maxn*160],R[maxn*160],sum[maxn*160],cnt;

inline int read(){
    register int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return (f==1)?x:-x;
}

inline void addedge(int x,int y){
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}

void update(int &now,int l,int r,int x,int v){
    if(!now) now=++cnt;
    sum[now]+=v;
    if(l == r) return ;
    int mid=(l+r)>>1;
    if(x <= mid) update(L[now],l,mid,x,v);
    else update(R[now],mid+1,r,x,v);
}

int query(int now,int Le,int Ri,int l,int r){
    if(!now) return 0;
    if(Le <= l && r <= Ri) return sum[now];
    int mid=(l+r)>>1,ans=0;
    if(Le <= mid) ans+=query(L[now],Le,Ri,l,mid);
    if(Ri > mid) ans+=query(R[now],Le,Ri,mid+1,r);
    return ans;
}

void dfs(int x,int f){
    dep[x]=dep[f]+1;
    st[x]=++tim;mp[tim]=x;
    for(int i=head[x],y;i;i=nxt[i]){
        y=to[i];
        if(y==f) continue;
        dfs(y,x);mp[++tim]=x;
    }
}

int cmp(int x,int y){
    return (dep[x]<dep[y])?x:y;
}

int LCA(int x,int y){
    int l=st[x],r=st[y];
    if(l>r) swap(l,r);
    int k=lg[r-l+1];
    return cmp(Min[l][k],Min[r-(1<<k)+1][k]);
}

int getdis(int x,int y){
    return dep[x]+dep[y]-2*dep[LCA(x,y)];
}

void getrt(int x,int f){
    siz[x]=1;maxp[x]=0;
    for(int i=head[x],y;i;i=nxt[i]){
        y=to[i];
        if(y==f||vis[y]) continue;
        getrt(y,x);siz[x]+=siz[y];
        maxp[x]=max(maxp[x],siz[y]);
    }
    maxp[x]=max(maxp[x],Sum-siz[x]);
    if(maxp[x]<maxp[rt]) rt=x;
}

void solve(int x,int f,int pre){
    fa[x]=f;vis[x]=1;
    for(int i=head[x],y;i;i=nxt[i]){
        y=to[i];
        if(vis[y]) continue;
        rt=0;Sum=(siz[x]>siz[y])?siz[y]:pre-siz[x];
        getrt(y,x);solve(rt,x,siz[x]);
    }
}

void modify(int u,int v){
    update(T[u+n],0,n,0,v);
    int x,y,dis;
    for(x=u;fa[x];x=fa[x]){
        y=fa[x];dis=getdis(u,y);
        update(T[y+n],0,n,dis,v);
        update(T[x],0,n,dis,v);
    }
}

int ask(int u,int k){
    int x,y,dis,ans=query(T[u+n],0,k,0,n);
    for(x=u;fa[x];x=fa[x]){
        y=fa[x];dis=getdis(u,y);
        if(dis>k) continue;
        ans+=query(T[y+n],0,k-dis,0,n);
        ans-=query(T[x],0,k-dis,0,n);
    }
    return ans;
}

int main()
{
    n=read(),m=read();
    int op,x,y,lastans=0;
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<n;i++){
        x=read(),y=read();
        addedge(x,y);addedge(y,x);
    }
    dfs(1,0);maxp[0]=inf;Sum=n;
    getrt(1,0);solve(rt,0,n);
    for(int i=2;i<=tim;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=tim;i++) Min[i][0]=mp[i];
    for(int j=1;j<=18;j++)
        for(int i=1;i+(1<<j)-1<=tim;i++) Min[i][j]=cmp(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
    for(int i=1;i<=n;i++) modify(i,a[i]);
    while(m--){
        op=read(),x=read()^lastans,y=read()^lastans;
        if(op==0) printf("%d\n",lastans=ask(x,y));
        else modify(x,y-a[x]),a[x]=y;
    }
    return 0;
}

还有一道更简单的动态点分治。。。烁烁的游戏

跟上一题差不多,上一题是资瓷单点修改,区间求和,这题是区间加,单点查询,标记永久化就行了。(其实区间查也可以的)

\(bzoj\) 上不卡常,好评。

\(Code\ Below:\)

#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
const int inf=0x3f3f3f3f;
int n,m,dep[maxn],lg[maxn<<1],Min[maxn<<1][19],st[maxn],mp[maxn<<1],tim;
int fa[maxn],maxp[maxn],siz[maxn],vis[maxn],rt,sum;
int head[maxn],to[maxn<<1],nxt[maxn<<1],tot;
int T[maxn<<1],ls[maxn*160],rs[maxn*160],lazy[maxn*160],cnt;

inline int read(){
	register int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return (f==1)?x:-x;
}

void update(int &now,int L,int R,int C,int l,int r){
	if(!now) now=++cnt;
	if(L <= l && r <= R){lazy[now]+=C;return;}
	int mid=(l+r)>>1;
	if(L <= mid) update(ls[now],L,R,C,l,mid);
	if(R > mid) update(rs[now],L,R,C,mid+1,r);
}

int query(int now,int l,int r,int x){
	if(!now) return 0;
	if(l == r) return lazy[now];
	int mid=(l+r)>>1;
	if(x <= mid) return lazy[now]+query(ls[now],l,mid,x);
	return lazy[now]+query(rs[now],mid+1,r,x);
}

inline void addedge(int x,int y){
	to[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}

void dfs(int x,int f){
	dep[x]=dep[f]+1;
	st[x]=++tim;mp[tim]=x;
	for(int i=head[x],y;i;i=nxt[i]){
		y=to[i];
		if(y==f) continue;
		dfs(y,x);mp[++tim]=x;
	}
}

int cmp(int x,int y){
	return (dep[x]<dep[y])?x:y;
}

int LCA(int x,int y){
	int l=st[x],r=st[y];
	if(l>r) swap(l,r);
	int k=lg[r-l+1];
	return cmp(Min[l][k],Min[r-(1<<k)+1][k]);
}

int getdis(int x,int y){
	return dep[x]+dep[y]-2*dep[LCA(x,y)];
}

void getrt(int x,int f){
	siz[x]=1;maxp[x]=0;
	for(int i=head[x],y;i;i=nxt[i]){
		y=to[i];
		if(y==f||vis[y]) continue;
		getrt(y,x);siz[x]+=siz[y];
		maxp[x]=max(maxp[x],siz[y]);
	}
	maxp[x]=max(maxp[x],sum-siz[x]);
	if(maxp[x]<maxp[rt]) rt=x;
}

void solve(int x,int f,int pre){
	fa[x]=f;vis[x]=1;
	for(int i=head[x],y;i;i=nxt[i]){
		y=to[i];
		if(vis[y]) continue;
		rt=0;sum=(siz[x]>siz[y])?siz[y]:pre-siz[x];
		getrt(y,x);solve(rt,x,siz[x]);
	}
}

void modify(int u,int k,int w){
	update(T[u+n],0,k,w,0,n);
	int x,y,dis;
	for(x=u;fa[x];x=fa[x]){
		y=fa[x];dis=getdis(u,y);
		if(dis>k) continue;
		update(T[y+n],0,k-dis,w,0,n);
		update(T[x],0,k-dis,w,0,n);
	}
}

int ask(int u){
	int x,y,dis,ans=query(T[u+n],0,n,0);
	for(x=u;fa[x];x=fa[x]){
		y=fa[x];dis=getdis(u,y);
		ans+=query(T[y+n],0,n,dis);
		ans-=query(T[x],0,n,dis);
	}
	return ans;
}

int main()
{
	n=read(),m=read();
	int x,y,w;char op;
	for(int i=1;i<n;i++){
		x=read(),y=read();
		addedge(x,y);addedge(y,x);
	}
	dfs(1,0);maxp[0]=inf;sum=n;
	getrt(1,0);solve(rt,0,n);
	for(int i=2;i<=tim;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=tim;i++) Min[i][0]=mp[i];
	for(int j=1;j<=18;j++)
		for(int i=1;i+(1<<j)-1<=tim;i++) Min[i][j]=cmp(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
	while(m--){
		op=getchar();
		while(!isalpha(op)) op=getchar();
		x=read();
		if(op=='Q') printf("%d\n",ask(x));
		else y=read(),w=read(),modify(x,y,w);
	}
	return 0;
}
posted @ 2019-01-25 09:06  Owen_codeisking  阅读(181)  评论(0编辑  收藏  举报