[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;
}