【Luogu P3241 & SP2939】[HNOI2015]开店 & QTREE5

总览

假设如今有一个点分治能够作的题,可是由于被强制在线(带修或者其余缘由)。显然有一种作法是每询问一次就点分一次,时间复杂度是\(O(mn\log n)\),是一个并不太优秀的作法。数组

考虑到每次点分治都须要从新找一次重心以及统计信息等等,而事实上因为树的形态并不会改变,也就是说重心实际上是并不会变化的。那么,找重心等等重复操做实际上是能够省下来的。数据结构

因而点分树这种结构就诞生了。ui

考虑点分治的过程,每次找到重心作完统计以后至关于把原树拆成了多个部分,递归地进行操做。那么咱们把每次的重心拿出来,剩下的各个部分分别找到重心以后,往当前的重心连一条边,就能够获得一颗重构后的树——点分树。spa

事实上因为重心的性质,这棵树的深度不会超过\(\log n\)层。所以咱们在统计答案或者修改时只须要从当前的点暴力地往上跳就能够了。code

因而只要在各个点上按照各个题目的须要维护一些奇奇怪怪的东西就能够解决问题了,一般会使用到线段树、树状数组、可删堆之类的数据结构。排序

例题1:SP2939 QTREE5

题意再也不赘述。递归

对于这一类树的形态固定,且答案没有对父子关系的要求的题目,咱们能够考虑点分治作法。get

思考一个简化的问题,假定没有修改颜色,有多组询问。qt

一个很显然的作法是,在点分治时对于当前的重心找到距离它最近的白点,而后加上当前重心与询问的\(v\)点之间的距离。it

注意一个细节,这里并不须要考虑最近的白点和\(v\)点在同一个子树的状况,由于这种状况获得的答案必定不是最优解。因此若是此题改成最大值,那么须要讨论的状况会变得很麻烦。

那么加上修改的操做以后呢?

建一棵点分树,而后对每个点维护一个数据结构,支持查询最小值,插入,删除三个操做。

那么咱们能够使用可删堆(好像线段树也能够)

因而这题就作完了。

#include<cstdio>
#include<queue>
using namespace std;
const int maxn=100005;
struct HEAP
{
	priority_queue<int,vector<int>,greater<int> > q1,q2;
	void maintain()
	{
		while (!q1.empty()&&!q2.empty()&&q1.top()==q2.top()) q1.pop(),q2.pop();
	}
	int top()
	{
		maintain();
		return q1.top();
	}
	bool empty()
	{
		maintain();
		return q1.empty();
	}
	void push(int x)
	{
		q1.push(x);
		maintain();
	}
	void erase(int x)
	{
		q2.push(x);
		maintain();
	}
}que[maxn];
struct DATA
{
	int to,next,val;
}edge[maxn<<1];
int head[maxn],cnt,n,q,u,v,size[maxn],dis[maxn],recsize,root,tot_size,fa[maxn],pts,opt,son[maxn],d[maxn],F[maxn],top[maxn];
bool tag[maxn],col[maxn];
inline int read()
{
	int ret=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') ret=ret*10+c-48,c=getchar();
	return ret;
}
void add(int u,int v)
{
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
void dfs1(int u,int f)
{
	size[u]=1;
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (v==f) continue;
		dis[v]=dis[u]+1;
		dfs1(v,u);
		size[u]+=size[v];
		if (size[v]>size[son[u]]) son[u]=v;
	}
} 
void dfs2(int u,int f,int t)
{
	F[u]=f;
	d[u]=d[f]+1;
	top[u]=t;
	if (son[u]) dfs2(son[u],u,t);
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (v==f||v==son[u]) continue;
		dfs2(v,u,v);
	}
}
inline int query_LCA(int x,int y)
{
	while (top[x]!=top[y])
	{
		if (d[top[x]]<d[top[y]]) swap(x,y);
		x=F[top[x]];
	}	
	return d[x]>d[y]?y:x;
}
void get_root(int u,int f)
{
	size[u]=1;int maxsize=0;
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (tag[v]||v==f) continue;
		get_root(v,u);
		size[u]+=size[v];
		maxsize=max(maxsize,size[v]);
	}
	maxsize=max(maxsize,tot_size-size[u]);
	if (maxsize<recsize)
	{
		recsize=maxsize;
		root=u;
	}
}
void get_size(int u,int f)
{
	size[u]=1;
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (tag[v]||v==f) continue;
		get_size(v,u);
		size[u]+=size[v];
	}
}
void build(int u)
{
	tag[u]=true;
	get_size(u,0);
	if (size[u]==1) return ;
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (tag[v]) continue;
		recsize=0x3f3f3f3f;
		tot_size=size[v];
		get_root(v,u);
		fa[root]=u;
		build(root);
	}
}
inline int get_dis(int x,int y)
{
	return dis[x]+dis[y]-2*dis[query_LCA(x,y)];
}
void update(int u,int c)
{
	if (u==0) return ;
	int t=get_dis(u,pts);
	if (c==1)
		que[u].push(t);
	else
		que[u].erase(t);
	update(fa[u],c);
} 
int query(int u)
{
	if (u==0) return 0x3f3f3f3f;
	int ret=0x3f3f3f3f;
	int t=get_dis(u,pts);
	if (!que[u].empty()) ret=min(que[u].top()+t,ret);
	return min(ret,query(fa[u]));
}
int main()
{
	n=read();
	for (int i=1;i<n;i++)
	{
		u=read(),v=read();
		add(u,v),add(v,u);
	}
	recsize=0x3f3f3f3f;
	tot_size=n;
	get_root(1,0);
	dfs1(root,0);
	dfs2(root,0,root);
	build(root);
	q=read();
	int now=0;
	for (int i=1;i<=q;i++)
	{
		opt=read(),pts=read();
		if (opt==0)
		{
			col[pts]=!col[pts];
			if (col[pts]==1) now++;
			else now--;
			update(pts,col[pts]);
		}
		else
		{
			if (col[pts]==1) printf("0\n");
			else if (now==0) printf("-1\n");
			else {printf("%d\n",query(pts));}
		}
	}
	return 0;
}

例题2:Luogu P3241 开店

一样先考虑暴力的作法。

对于每一个点记录其子树内节点到本身的距离,统计答案时加上到询问点的距离便可。

能够考虑在对年龄离散化后使用线段树,这里使用的是更暴力的vector。

对于每个点开一个vector,记录其下属每个节点到它的距离以及对应节点的年龄。

排序后就能够利用前缀和直接求出对应区间的和。

这一题有一个须要注意的地方,若是询问点和另外一个端点来自重心的同一棵子树就会出现统计重复的问题。因此在统计时要去掉询问点所在子树对当前重心的贡献。

所以每一个节点须要维护两个vector,一个是下属节点到自身的信息,一个是下属节点到自身父亲的信息。

#include<cstdio>
#include<vector>
#include<algorithm>
#define mid ((l+r)>>1)
#define ll long long
using namespace std;
const int maxn=1.5e5+5;
struct DATA
{
	int to,next;ll val; 
}edge[maxn<<1];
struct REC
{
	ll val;int id;
	bool operator< (const REC &x) const
	{
		return id<x.id;
	}
};
int cnt,head[maxn],F[maxn],son[maxn],size[maxn],d[maxn],top[maxn];
bool tag[maxn];
ll dis[maxn],w;
vector<REC> vec[2][maxn];
int tot_size,recsize=0x3f3f3f3f,root,fa[maxn],pts,last,A,AA,BB,a[maxn],n,Q,u,v;
ll ans;
void add(int u,int v,ll w)
{
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	edge[cnt].val=w;
	head[u]=cnt;
}
void dfs1(int u,int f)
{
	F[u]=f;
	d[u]=d[f]+1;
	size[u]=1;
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (v==f) continue;
		dis[v]=dis[u]+edge[i].val;
		dfs1(v,u);
		size[u]+=size[v];
		if (size[son[u]]<size[v]) son[u]=v;
	}
}
void dfs2(int u,int t)
{
	top[u]=t;
	if (son[u]) dfs2(son[u],t);
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (v==F[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
int query_LCA(int x,int y)
{
	while (top[x]!=top[y])
	{
		if (d[top[x]]<d[top[y]]) swap(x,y);
		x=F[top[x]];
	}
	return d[x]<d[y]?x:y;
}
inline ll getdis(int x,int y)
{
	return dis[x]+dis[y]-2*dis[query_LCA(x,y)];
}
void get_root(int u,int f)
{
	size[u]=1;int maxsize=0;
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (v==f||tag[v]) continue;
		get_root(v,u);
		size[u]+=size[v];
		maxsize=max(size[v],maxsize);
	}
	maxsize=max(maxsize,tot_size-size[u]);
	if (maxsize<recsize)
	{
		recsize=maxsize;
		root=u;
	}
}
void getsize(int u,int f,int sum)
{
	size[u]=1;
//	REC tmp=REC{getdis(u,root),a[u]};
	vec[0][root].push_back(REC{sum,a[u]});
	if (fa[root]) vec[1][root].push_back(REC{getdis(u,fa[root]),a[u]});
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (tag[v]||v==f) continue;
		getsize(v,u,sum+edge[i].val);
		size[u]+=size[v];
	}
}
void build(int x)
{
	tag[x]=true;
	getsize(x,0,0);
	for (int i=head[x];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (tag[v]) continue;
		recsize=0x3f3f3f3f;
		tot_size=size[v];
		get_root(v,x);
		fa[root]=x;
		build(root);
	}
}
inline int read()
{
	int ret=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') ret=ret*10+c-48,c=getchar();
	return ret;
}
inline ll READ()
{
	ll ret=0;char c=getchar();
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') ret=ret*10+c-48,c=getchar();
	return ret;
}
signed main()
{
	n=read(),Q=read(),A=read();
	for (int i=1;i<=n;i++)
	{
		a[i]=read();
		a[i]++;
	}
	for (int i=1;i<n;i++)
	{
		u=read(),v=read(),w=READ();
		add(u,v,w),add(v,u,w);
	}
	dfs1(1,0); 
	dfs2(1,0);
	tot_size=n;
	get_root(1,0);
	tag[root]=true;
	build(root);
//	for (int i=0;i<vec[0][2].size()-1;i++) printf("Case%d: %d %d\n",i+1,vec[0][2][i].val,vec[0][2][i].id);
	for (int i=1;i<=n;i++)
	{
		sort(vec[0][i].begin(),vec[0][i].end());
		sort(vec[1][i].begin(),vec[1][i].end());
		vec[0][i].push_back(REC{0,A+1});
		vec[1][i].push_back(REC{0,A+1});
		for (int j=vec[0][i].size()-2;j>-1;j--) vec[0][i][j].val+=vec[0][i][j+1].val;
		for (int j=vec[1][i].size()-2;j>-1;j--) vec[1][i][j].val+=vec[1][i][j+1].val;
	}
//	puts("fuckyou");
//	for (int i=0;i<vec[0][2].size();i++) printf("Case%d: %d %d\n",i,vec[0][2][i].val,vec[0][2][i].id);
	for (int i=1;i<=Q;i++)
	{
		u=read(),AA=read(),BB=read(); 
		int L,R;
		L=min((AA+ans)%A,(BB+ans)%A),R=max((AA+ans)%A,(BB+ans)%A);
		L++,R++;
		int x=u;
		ll ret=0;
		while (x!=0)
		{
			int LL=lower_bound(vec[0][x].begin(),vec[0][x].end(),REC{0,L})-vec[0][x].begin();
			int RR=upper_bound(vec[0][x].begin(),vec[0][x].end(),REC{0,R})-vec[0][x].begin();
			ret+=vec[0][x][LL].val-vec[0][x][RR].val+(RR-LL)*getdis(x,u);
			if (fa[x]!=0) 
			{
				int LL=lower_bound(vec[1][x].begin(),vec[1][x].end(),REC{0,L})-vec[1][x].begin();
				int RR=upper_bound(vec[1][x].begin(),vec[1][x].end(),REC{0,R})-vec[1][x].begin();
				ret-=vec[1][x][LL].val-vec[1][x][RR].val+(RR-LL)*getdis(fa[x],u);
			}
			x=fa[x];
		}
		printf("%lld\n",ans=ret);
	}
	return 0;
}
相关文章
相关标签/搜索