[CSP校内集训]ac(树上启发式合并)

题意

有一棵树,每个节点\(i\)有一定的容量\(k_i\)(只能装\(k_i\)个颜色);有\(m\)次操作,每次给\(x\)\(1\)路径上的所有点加上一个颜色\(c\);修改操作完成后询问每个节点有多少种不同的颜色\((n,m,k_i \leq 10^5)\)

思路

30pts数据小可以直接暴力跳

另外40pts有\(k_i=10^5\)可以类比雨天的尾巴


可以看出来对一个点的答案有影响的操作都在它的子树中

这种问题,显然要么维护颜色(值域线段树),要么维护时间(修改操作的顺序),而值域线段树考虑不到\(k\)的约束,只有40pts,所以考虑维护时间

于是按修改操作的顺序作为下标维护线段树,一个点维护两个值:\(siz\):该子树有多少个操作;\(sum\):该子树中实际有贡献的操作数

什么叫实际有贡献的操作?就是说同一个子树中,如果两个同样颜色的操作先后发生,那后发生的操作就没有贡献,也就是\(siz+1\)\(sum\)不变

有了这两个变量,在线段树上按照\(k_i\)二分即可求出\(i\)节点的答案

但不可能每个点都建一棵线段树,由于一个点只考虑其子树中的操作,线段树可以自底向上合并,于是就成了树上启发式合并,保证所有操作的复杂度不基于重儿子就可以保证复杂度没问题

这道题思路类似,就是把\(trie\)换成了时间上的线段树

时间复杂度\(O(nlog^2n)\)

Code

#include<bits/stdc++.h>
#define N 100005
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
typedef long long ll;
int n,m,q,k[N],ans[N];
int sum[N<<2],size[N<<2],sig[N<<2];
int son[N],fa[N],t[N<<2];
vector< pair<int,int> > co[N];
map<int,int> mp;//颜色还有负数,cao 
int colsum=0;

struct Edge
{
	int next,to;
}edge[N<<1];int head[N],cnt=1;

void add_edge(int from,int to)
{
	edge[++cnt].next=head[from];
	edge[cnt].to=to;
	head[from]=cnt;
}
template <class T>
void read(T &x)
{
	char c; int sign=1;
	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
	while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void dfs1(int rt)
{
	size[rt]=1;
	for(int i=head[rt];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa[rt]) continue;
		fa[v]=rt;
		dfs1(v);
		size[rt]+=size[v];
		if(size[son[rt]]<size[v]) son[rt]=v;
	}
}

void pd(int rt)
{
	if(!sig[rt]) return;
	sig[rt<<1]=sig[rt<<1|1]=1;
	size[rt<<1]=size[rt<<1|1]=sum[rt<<1]=sum[rt<<1|1]=0;
	sig[rt]=0;
}
void modify(int rt,int l,int r,int x,int val,int siz)//颜色数量,时间数量 
{
	if(l==r) { sum[rt]+=val; size[rt]+=siz; return; }
	int mid=(l+r)>>1;
	pd(rt);
	if(x<=mid) modify(rt<<1,l,mid,x,val,siz);
	else modify(rt<<1|1,mid+1,r,x,val,siz);
	sum[rt]=sum[rt<<1]+sum[rt<<1|1];
	size[rt]=size[rt<<1]+size[rt<<1|1];
}
int query(int rt,int l,int r,int k)
{
	if(k<=0) return 0;
	if(l==r) return sum[rt];
	int mid=(l+r)>>1;
	pd(rt);
	if(size[rt<<1]<=k) return sum[rt<<1] + query(rt<<1|1,mid+1,r,k-size[rt<<1]);
	return query(rt<<1,l,mid,k);
}
void add(int rt)
{
	for(int i=0,c=co[rt].size();i<c;++i)
	{
		int col=co[rt][i].first,tim=co[rt][i].second;
		if(!t[col])//第一次加入该颜色 
		{
			t[col]=tim;
			modify(1,1,m,tim,1,0);
		}
		else if(t[col]>tim)
		{
			modify(1,1,m,t[col],-1,0);
			modify(1,1,m,tim,1,0);
			t[col]=tim;
		}
		modify(1,1,m,tim,0,1);
	}
}
void clr(int rt)
{
	size[1]=sum[1]=sig[1]=1;
	for(int i=0,c=co[rt].size();i<c;++i) t[co[rt][i].first]=0;
}
void cv(int x,int y)
{
	for(int i=0,c=co[y].size();i<c;++i) co[x].push_back(co[y][i]);
	co[y].clear();
}
void dfs(int rt)
{
	for(int i=head[rt];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa[rt] || v==son[rt]) continue;
		dfs(v); clr(v);
	}
	if(son[rt]) dfs(son[rt]);
	add(rt);
	for(int i=head[rt];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa[rt] || v==son[rt]) continue;
		add(v);
	}
	ans[rt]=query(1,1,m,k[rt]);
	if(son[rt])
	{
        cv(son[rt],rt);
    	swap(co[rt],co[son[rt]]);
//		cv(rt,son[rt]);直接这样复杂度是错的qwq 
    	for(int i=head[rt];i;i=edge[i].next)
        {
        	int v=edge[i].to;
        	if(v!=fa[rt]) cv(rt,v);
        }
    }
}

int main()
{
	freopen("ac.in","r",stdin);
	freopen("ac.out","w",stdout);
	read(n);
	for(int i=1;i<n;++i)
	{
		int x,y;
		read(x);read(y);
		add_edge(x,y);
		add_edge(y,x);
	}
	dfs1(1);
	for(int i=1;i<=n;++i) read(k[i]);
	read(m);
	for(int i=1;i<=m;++i)
	{
		int x,c;
		read(x);read(c);
		if(!mp[c]) mp[c]=++colsum;
		c=mp[c];
		co[x].push_back(make_pair(c,i));
	}
	memset(size,0,sizeof(size));
	dfs(1);
	read(q);
	while(q--)
	{
		int x; read(x);
		printf("%d\n",ans[x]);
	}
	return 0;
}
posted @ 2019-10-30 08:34  擅长平地摔的艾拉酱  阅读(235)  评论(0编辑  收藏  举报
/*取消选中*/