【洛谷P5311】【点分树】【二维偏序】YNOI2011D1T3

【题目描述】
音无彩名给了你一棵n个节点的树,每个节点有一种颜色,有m次查询操作
查询操作给定参数l r x,需输出:
将树中编号在[l,r]内的所有节点保留,x所在联通块中颜色种类数
每次查询操作独立

【思路】

首先有一个性质:对于一次询问,一定可以在点分树上找到一个点,满足这个点在本次询问中和x在同一个连通块中,且x所在的整个连通块都是该点在点分树中的一个子树。证明可以考虑反证法。那么我们把询问都离线,放到点分树上对应的点,在这个位置处理这个询问。现在,我们枚举每个点,处理与它有关的询问。对于它点分树的子树上的每个点,我们可以求出它们和分治中心路径上最大的编号和最小的编号的点,于是一个点的信息可以用这样一个三元组表示: ( m n , m x , c o l ) (mn,mx,col) (mn,mx,col)。现在问题变成了一个偏序问题:一个点对一个询问有贡献当且仅当 m n > = l 且 m x < = r mn>=l且mx<=r mn>=lmx<=r,因为满足这个条件的点就和分治中心联通,就一定和x联通。所以我们可以把所有点和询问按mn降序排序,保证第一个限制满足。然后再用树状数组以mx为下标即可实现满足第二个限制。问题在于我们不能重复统计一个颜色的贡献。对于每一种颜色,我们可以考虑在树状数组里只维护它的mx的最小值。因为对于两个均满足 m n > = l mn>=l mn>=l的同色的点,如果mx小的那个点对答案都没有贡献,那么另一个点必然没有贡献,所以这个方法是正确的。

代码:

#include<bits/stdc++.h>
#define re register
#define F(i,a,b) for(int re i=a;i<=b;++i)
#define D(i,a,b) for(int re i=a;i>=b;--i)
using namespace std;
const int N=1e5+5;
inline int red(){
    int re data=0;bool w=0;char re ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return w?-data:data;
}
int n,m,a,b,col[N];
vector<int>g[N];
struct node{int mn,mx,rt,op;};
vector<node>t[N],q[N];
int siz[N],mz=1e9,rt=0;
bool vis[N];
inline void cmax(int&x,const int y){(x<y)&&(x=y);}
inline void cmin(int&x,const int y){(x>y)&&(x=y);}
void findrt(int u,int fa,const int tot){
	siz[u]=1;int v;int mx=0;
	D(i,g[u].size()-1,0){
		v=g[u][i];
		if(v!=fa&&!vis[v]){
			findrt(v,u,tot);siz[u]+=siz[v];
			cmax(mx,siz[v]);
		}
	}
	cmax(mx,tot-siz[u]);
	if(mx<mz)mz=mx,rt=u;
}
void dfs(int u,int mn,int mx,int fa){
	cmin(mn,u);cmax(mx,u);int v;siz[u]=1;
	q[rt].push_back((node){mn,mx,col[u],0});
	t[u].push_back((node){mn,mx,rt,0});
	D(i,g[u].size()-1,0){
		v=g[u][i];
		if(v!=fa&&!vis[v])
			dfs(v,mn,mx,u),siz[u]+=siz[v];
	}
}
void solve(int u){
	vis[u]=1;dfs(u,1e9,-1e9,0);int v;
	D(i,g[u].size()-1,0){
		v=g[u][i];
		if(!vis[v])findrt(rt=v,u,mz=siz[v]),solve(rt);
	}	
}
int c[N];
inline void add(int x,int v){while(x<=n)c[x]+=v,x+=x&-x;}
inline int query(int x){
	int ret=0;
	while(x)ret+=c[x],x^=x&-x;
	return ret;
}
int tim[N],ans[N];
inline bool cmp(node a,node b){return a.mn>b.mn||(a.mn==b.mn&&a.op<b.op);}
int main()
{
	n=red();m=red();
	F(i,1,n)col[i]=red();
	F(i,2,n){
		a=red(),b=red();
		g[a].push_back(b);
		g[b].push_back(a);
	}findrt(1,0,mz=n);solve(rt);
	F(i,1,m){
		int l=red(),r=red();a=red();
		F(j,0,t[a].size()-1)
			if(t[a][j].mn>=l&&t[a][j].mx<=r){q[t[a][j].rt].push_back((node){l,r,i,1});break;}
	}memset(tim,127,sizeof(tim));
	F(i,1,n){
		sort(q[i].begin(),q[i].end(),cmp);
		F(j,0,q[i].size()-1){
			node now=q[i][j];
			if(now.op)ans[now.rt]=query(now.mx);
			else if(tim[now.rt]>now.mx){
				if(tim[now.rt]<=1e5)add(tim[now.rt],-1);
				add(tim[now.rt]=now.mx,1);
			}
		}F(j,0,q[i].size()-1){
			node now=q[i][j];
			if(now.op)continue;
			tim[now.rt]=1e9;
			for(int re k=now.mx;k<=n;k+=k&-k)c[k]=0;
		}
	}F(i,1,m)cout<<ans[i]<<"\n";
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值