[NOISG 2022 Qualification] Dragonfly 解题报告
[NOISG 2022 Qualification] Dragonfly 解题报告
简要题意
一棵有 \(n\) 个节点的树,每个节点有一个颜色 \(s_i\) 和权值 \(b_i\)。现在有 \(d\) 个形如 \(h_i\) 的询问,对于每个询问,你需要回答简单路径 \((1,h_i)\) 上权值不为 \(0\) 点的颜色种类;询问后将 \((1,h_i)\) 上所有点权不为 \(0\) 的点的点权减一。
数据范围:\(n \le 2\times 10^5\),\(d\le 2 \times 10^6\)
分析
考虑到 \(n\) 和 \(d\) 的存在一定差距,因此从点的角度考虑。
因为我们点权是随询问递减的,所以一个点只能对一段区间的询问产生贡献。
因此考虑维护 \(lst_i\) 表示 \(i\) 号节点可以做出贡献的最大询问编号。注意到这东西具有单调性,因此考虑二分。对每一个点,二分已经进行的询问数。如果其子树内已经进行的询问次数大于 \(b_i\),那么返回 false;否则返回 true 。但是这东西是 \(O(nd\log d)\) 的( \(n\) 是枚举点,\(d\log d\) 是单次二分)。
注意到瓶颈是每个点二分都要重复插入询问,因此考虑整体二分,这样时间复杂度就优化到了 \(O(d\log^2 d)\) 的。
之后考虑怎么处理询问。注意到每个询问都是根到节点的路径,因此考虑维护当前每种颜色可以贡献的最大询问编号,并在树状数组上更新每个询问编号的答案。搜索到一个节点时,维护当前节点的询问即可。
总时间复杂度 \(O(dlog^2d)\)
正解
题解给出了更优的时间复杂度,因为一个节点子树内的所有询问编号可以通过线段树合并维护的,因此在合并出的线段树上跑线段树二分求第 \(b_i\) 大就可以了。
时间复杂度 \(O(dlogd)\)
代码
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Inf (1ll<<60)
#define For(i,s,t) for(int i=s;i<=t;++i)
#define Down(i,s,t) for(int i=s;i>=t;--i)
#define ls (i<<1)
#define rs (i<<1|1)
#define lowbit(x) ((x)&(-(x)))
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
inline ll min(ll x,ll y){return x<y?x:y;}
inline ll max(ll x,ll y){return x>y?x:y;}
inline int read(){
register int x=0,f=1;
char c=getchar();
while(c<'0' || '9'<c) f=(c=='-')?-1:1,c=getchar();
while('0'<=c && c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
return x*f;
}
void write(int x){
if(x>=10) write(x/10);
putchar(x%10+'0');
}
const int N=2e6+100,M=2e6+100,P=2e6;
int n,m,a[N],s[N],lst[N],p[M],ans[M],c[M];
struct Edge{int from,to;}e[N<<1];
int num,h[N];
void add_edge(int f,int t){e[++num].from=h[f],e[num].to=t,h[f]=num;}
int dfn[N],sz[N],dfu;
void dfs(int u,int fa){
dfn[u]=++dfu,sz[u]=1;
for(int i=h[u],v;i;i=e[i].from){
v=e[i].to;
if(v!=fa)
dfs(v,u),sz[u]+=sz[v];
}
}
int id[N],idl[N],idr[N];
inline void add(int x,int k){
for(;x && x<=P;x+=lowbit(x))
c[x]+=k;
}
inline int ask(int x){
int ans=0;
for(;x;x-=lowbit(x))
ans+=c[x];
return ans;
}
//二分区间 [l,r] 询问下标[x,y]
void solve(int l,int r,int x,int y){
if(l==r){
For(i,x,y) lst[id[i]]=l;
return;
}
//printf("In [%d,%d] [%d,%d]:\n",l,r,x,y);
int mid=l+r>>1,res,cntl=0,cntr=0;
For(i,l,mid) add(dfn[p[i]],1);
For(i,x,y){
res=ask(dfn[id[i]]+sz[id[i]]-1)-ask(dfn[id[i]]-1);
//printf("%d gain %d\n",id[i],res);
if(res>=a[id[i]])
idl[++cntl]=id[i];
else
idr[++cntr]=id[i],a[id[i]]-=res;
}
For(i,l,mid) add(dfn[p[i]],-1);
For(i,1,cntl) id[x+i-1]=idl[i];//,printf("%d ",idl[i]);
//putchar('\n');
For(i,1,cntr) id[x+cntl+i-1]=idr[i];//,printf("%d ",idr[i]);
//putchar('\n');
solve(l,mid,x,x+cntl-1);
solve(mid+1,r,x+cntl,y);
}
int range[N],hd[N],ask_id[M],nxt[M],ask_num;
void add_quest(int x,int p){nxt[++ask_num]=hd[p],ask_id[ask_num]=x,hd[p]=ask_num;}
void dfs1(int u,int fa){
int old=-1;
if(lst[u]>range[s[u]]){
old=range[s[u]];
if(old) add(m-old+1,-1);
range[s[u]]=lst[u];
add(m-lst[u]+1,1);
//printf("color %d update to %d at point %d\n",s[u],lst[u],u);
}
for(int i=hd[u];i;i=nxt[i]){
ans[ask_id[i]]=ask(m-ask_id[i]+1);
//printf("%d ask and get %d at point %d\n",ask_id[i],ans[ask_id[i]],u);
}
for(int i=h[u];i;i=e[i].from)
if(e[i].to!=fa)
dfs1(e[i].to,u);
if(old!=-1){
add(m-lst[u]+1,-1);
range[s[u]]=old;
if(old) add(m-old+1,1);
//printf("color %d update to %d at point %d\n",s[u],old,u);
}
}
int main()
{
//freopen("woodpecker.in","r",stdin);
//freopen("woodpecker.out","w",stdout);
n=read(),m=read();
For(i,1,n) a[i]=read();
For(i,1,n) s[i]=read();
For(i,1,m) p[i]=read();
int u,v;
For(i,2,n) u=read(),v=read(),add_edge(u,v),add_edge(v,u);
dfs(1,0);
For(i,1,n) id[i]=i;
solve(0,m,1,n);
memset(c,0,sizeof(c));
//For(i,1,n) printf("lst[%d]=%d\n",i,lst[i]);
For(i,1,m) add_quest(i,p[i]);
dfs1(1,0);
For(i,1,m)
printf("%d ",ans[i]);
return 0;
}

浙公网安备 33010602011771号