【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;
}
相關文章
相關標籤/搜索