• loj3329 「WC2020」有根树


    • 一个性质:\(C\) 一定是 \(S\) 集合中 \(w\) 值最大的若干个点(记作集合 \(A\))和根形成的连通块。
    • 考虑反证:若 \(\exists x,x\in S\backslash A\),那 \(x\) 必为 \(A\) 中某个点(设为 \(y\))的祖先,所以 \(w_x>w_y\),不满足 \(A\) 的定义。
    • \(B=S-A\),我们强制钦定 \(|A|\geq\max_{x\in B} w_x\)。每一次询问要求的答案就是当前的 \(|A|\)
    • 根据定义,显然有:\(\max_{x\in B} w_x\leq \min_{x\in A} w_x\)。记 \(ans=|A|\)

    \(O(n \log^2 n)\) 做法

    • 对原树进行轻重链剖分。
    • 线段树维护每个节点子树内的 \(\max_{x\in B} w_x\)\(\min_{x\in A} w_x\)(分别记为 \(mx[now],mn[now]\)),询问时调整。
    • 初始时,\(mx[now]=-INF,mn[now]=INF\),代表子树中没有在 \(S\) 中的点。
    • 加入一个数 \(x\) 时,先将它加入到 \(B\) 中。那么相当于在将 \(x\)\(mx+=INF\)。若原来的值为 \(-INF+k\),那么现在的值为 \(k\)\(k\) 即为 \(x\) 子树在 \(S\) 中的点的个数)。然后将 \(x\) 到根路径上的所有 \(w++\)
    • 删除一个数 \(x\) 时,若当前位置 \(mx<0\),说明并不在 \(A\) 中,就从 \(B\) 中删除,\(mx-=INF\)。否则从 \(A\) 中删除,\(mn+=INF\),同时 \(ans--\)。(\(A\) 的大小减少了 \(1\))。然后将 \(x\) 到根路径上的所有 \(w--\)
    • \(\max_{x\in B} w_x> \min_{x\in A} w_x\),再将 \(B\) 中的最大值移入 \(A\) 中,使得其满足 \(A\) 的定义。
    • \(|A|<\max_{x\in B} w_x\),再将 \(B\) 中的最大值移入 \(A\) 中。(已强制钦定 \(|A|\geq\max_{x\in B} w_x\)
    • \(|A|>min_{x\in A} w_x\),当前答案必大于 \(min_{x\in A} w_x\),那么将 \(A\) 中的最小值移到 \(B\) 中,答案必定不会变劣。
    • 最后得到的 \(|A|\) 就是答案。
    /*********************************************************************
    	Problem:「WC2020」有根树
    	Author:hydd
    	Date:2020/8/13
    *********************************************************************/
    #include<cstdio>
    #include<algorithm>
    #define File(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
    using namespace std;
    const int INF=0x3f3f3f3f;
    char Getchar(){
    	static char now[1<<20],*A,*B;
    	if (B==A){
    		B=(A=now)+fread(now,1,1<<20,stdin);
    		if (B==A) return EOF;
    	}
    	return *A++;
    }
    int read(){
    	int x=0,f=1;
    	char ch=Getchar();
    	while (ch<'0'||ch>'9'){
    		if (ch=='-') f=-1;
    		ch=Getchar();
    	}
    	while (ch<='9'&&ch>='0') x=x*10+ch-'0',ch=Getchar();
    	return x*f;
    }
    int n,q,ans,fa[510000],sz[510000];
    int dtime,son[510000],top[510000],dfn[510000];
    int edgenum,vet[1100000],val[1100000],Next[1100000],Head[510000];
    void addedge(int u,int v){
    	vet[++edgenum]=v;
    	Next[edgenum]=Head[u];
    	Head[u]=edgenum;
    }
    void dfs(int u,int f){
    	fa[u]=f; sz[u]=1; son[u]=0;
    	for (int e=Head[u],v;e;e=Next[e]){ v=vet[e];
    		if (v==f) continue;
    		dfs(v,u); sz[u]+=sz[v];
    		if (sz[v]>sz[son[u]]) son[u]=v;
    	}
    }
    void dfs2(int u,int tp){
    	top[u]=tp; dfn[u]=++dtime;
    	if (son[u]) dfs2(son[u],tp);
    	for (int e=Head[u],v;e;e=Next[e]){ v=vet[e];
    		if (v==fa[u]||v==son[u]) continue;
    		dfs2(v,v);
    	}
    }
    
    //namespace Segment Tree
    int mn[2100000],mx[2100000],tag[2100000];
    inline void pushup(int now){
    	mn[now]=min(mn[now<<1],mn[now<<1|1]);
    	mx[now]=max(mx[now<<1],mx[now<<1|1]);
    }
    inline void add(int now,int v){
    	mn[now]+=v; mx[now]+=v;
    	tag[now]+=v;
    }
    inline void pushdown(int now){
    	if (tag[now]){
    		add(now<<1,tag[now]);
    		add(now<<1|1,tag[now]);
    		tag[now]=0;
    	}
    }
    void change(int now,int l,int r,int x,int v){
    	if (l==r){
    		if (v>0) mx[now]+=INF;//B中加入
    		else if (mx[now]<0) mn[now]+=INF,ans--;//A中删除
    		else mx[now]-=INF;//B中删除
    		return;
    	}
    	pushdown(now);
    	int mid=(l+r)>>1;
    	if (x<=mid) change(now<<1,l,mid,x,v);
    	else change(now<<1|1,mid+1,r,x,v);
    	pushup(now);
    }
    void change(int now,int l,int r,int x,int y,int v){
    	if (l==x&&r==y){
    		add(now,v);
    		return;
    	}
    	pushdown(now);
    	int mid=(l+r)>>1;
    	if (y<=mid) change(now<<1,l,mid,x,y,v);
    	else if (x>mid) change(now<<1|1,mid+1,r,x,y,v);
    	else { change(now<<1,l,mid,x,mid,v); change(now<<1|1,mid+1,r,mid+1,y,v);}
    	pushup(now);
    }
    void BtoA(int now,int l,int r){
    	if (l==r){
    		ans++;
    		mx[now]-=INF; mn[now]-=INF;
    		return;
    	}
    	pushdown(now);
    	int mid=(l+r)>>1;
    	if (mx[now]==mx[now<<1]) BtoA(now<<1,l,mid);
    	else BtoA(now<<1|1,mid+1,r);
    	pushup(now);
    }
    void AtoB(int now,int l,int r){
    	if (l==r){
    		ans--;
    		mx[now]+=INF; mn[now]+=INF;
    		return;
    	}
    	pushdown(now);
    	int mid=(l+r)>>1;
    	if (mn[now]==mn[now<<1]) AtoB(now<<1,l,mid);
    	else AtoB(now<<1|1,mid+1,r);
    	pushup(now);
    }
    int main(){
    //	File("tree");
    	n=read(); int op,u,v;
    	for (int i=1;i<n;i++){
    		u=read(); v=read();
    		addedge(u,v); addedge(v,u);
    	}
    	dfs(1,0); dfs2(1,1);
    	q=read();
    	/*
    		答案必定是S中w最大的若干个点
    		我们选择的S中w最大的若干个点的子集为A,B=S-A
    		满足B中最大值<=A中最小值
    	*/
    	for (int i=1;i<=4*n+1;i++) mn[i]=INF,mx[i]=-INF;
    	while (q--){
    		op=read(); v=read(); if (op==2) op=-1;
    		change(1,1,n,dfn[v],op);
    		for (;v;v=fa[top[v]]) change(1,1,n,dfn[top[v]],dfn[v],op);
    		/*if (op==1){
    			while (mn[1]<mx[1]) BtoA(1,1,n);//A中最小值<B中最大值
    		} else{*/
    		while (mn[1]<mx[1]) BtoA(1,1,n);//A中最小值<B中最大值
    		//}
    		while (ans<mx[1]) BtoA(1,1,n);//钦定|A|>=mx[1]
    		while (ans>mn[1]) AtoB(1,1,n);//答案已经比mn[1]大了,把它移到B答案不会变劣
    		printf("%d\n",ans);
    	}
    	return 0;
    }
    

    \(O(n\log n)\) 做法

    • 对原树进行轻重链剖分后,维护每条重链上:
      • 重链上的点的个数(记作 \(tot\))。
      • 最靠下的在 \(A\) 中的点与链顶深度之差 \(+1\)。记作 \(up\)),如果没有,设为 \(0\)\(up\)\(w\) 值(记作 \(up\_w\))。
      • 最靠上的在 \(B\) 中的点与链顶深度之差 \(+1\)(记作 \(dw\)),如果没有,设为 \(tot+1\)\(dw\)\(w\) 值(记作 \(dw\_w\))。
    • 维护一个 \(cnt=|A|\),再维护 \(lim\),要求 \(\min_{x\in A} x>lim,\max_{x\in B}\leq lim\)

    \(S\) 中插入点 \(x\)

    • 找到 \(x\) 所在的重链,先更新链上的 \(up\_w/dw\_w\)\(+1\)
    • 如果现在的 \(dw\_w>lim\),就可以把它移动到 \(A\) 中(而必定只有一个点会移到 \(A\) 中,因为链上从上到下的 \(w\) 值必定是严格递减的)。(此条件有可能满足只有可能最靠上的在 \(B\) 中的点在 \(x\) 上方)
    • 否则,如果当前最靠上的在 \(B\) 中的点在 \(x\) 下方,那么:
      • 如果 \(w_x\leqslant lim\),那么显然有 \(t-fa[x]\) 链上所有点都在 \(A\) 中,\(son[x]-\cdots\) 链上所有点都在 \(B\) 中,且当前点可以加入 \(B\),就可以考虑更新 \(up\)
      • 如果 \(w_x>lim\),那么显然有 \(t-fa[x]\) 链上所有点都在 \(A\) 中,\(son[x]-\cdots\) 链上所有点都在 \(B\) 中。且当前点可以加入 \(A\),就可以考虑更新 \(dw\)
    • 接下来,就是更新到根路径上的其他重链的 \(up\_w/dw\_w\),然后看要不要上移分界点即可。
    • 最后,当 \(cnt>lim\),不断将 \(A\) 中值为 \(lim\) 的点移到 \(B\) 中即可。

    \(S\) 中删除点 \(x\)

    • 找到 \(x\) 所在的重链,先更新链上的 \(up\_w/dw\_w\)\(-1\)。设 \(d\) 为当前点的深度与链顶深度之差 \(+1\)
    • 如果现在的 \(d\leqslant up[t]\),说明它现在在 \(A\) 中,删了之后要将 \(cnt--\)。同时如果 \(d=up[t]\),说明它是 \(A\) 中深度最大的点,删去之后要重新求深度最大的点。
    • 如果现在的 \(d=dw[t]\),说明它为 \(B\) 中深度最小的点,删去之后要重新求深度最小的点。(在 \(B\) 中但非深度最小的点删去没有影响)。
    • 然后,若 \(up_w[t]\leq lim\)\(up[t]\neq 0\) (这个点存在),那么就可以把它移动到 \(B\) 中,\(cnt--\)
    • 接下来,就是更新到根路径上的其他重链的 \(up\_w/dw\_w\),然后看要不要下移分界点即可。
    • 最后,当 \(cnt<lim\),不断将 \(B\) 值为 \(lim\) 的点移到 \(A\) 中即可。
    /*********************************************************************
    	Problem:「WC2020」有根树
    	Author:hydd
    	Date:2020/8/13-2020/8/14
    *********************************************************************/
    #include<cstdio>
    #include<algorithm>
    #include<set>
    #define File(x) freopen(x".in","r",stdin);//freopen(x".out","w",stdout)
    using namespace std;
    const int INF=0x3f3f3f3f;
    const int MAXN=510000;
    const int MAXM=1100000;
    int n,q;
    namespace IO{
    	const int LEN=1<<20;
    	char Getchar(){
    		static char now[LEN],*A,*B;
    		if (B==A){
    			B=(A=now)+fread(now,1,LEN,stdin);
    			if (B==A) return EOF;
    		}
    		return *A++;
    	}
    	int read(){
    		int x=0,f=1;
    		char ch=Getchar();
    		while (ch<'0'||ch>'9'){
    			if (ch=='-') f=-1;
    			ch=Getchar();
    		}
    		while (ch<='9'&&ch>='0') x=x*10+ch-'0',ch=Getchar();
    		return x*f;
    	}
    	char pbuf[LEN],*pp=pbuf;
    	void pc(const char c) {
    		if (pp-pbuf==LEN) fwrite(pbuf,1,LEN,stdout),pp=pbuf;
    		*pp++=c;
    	}
    	void write(int x){
    		static int sta[35];
    		if (x<0){ pc('-'); x=-x;}
    		int top=0;
    		do{
    			sta[top++]=x%10;
    			x/=10;
    		} while (x);
    		while (top) pc(sta[--top]+'0');
    	}
    	void IOflush(){ fwrite(pbuf,1,pp-pbuf,stdout);}
    } using namespace IO;
    namespace Graph{
    	int edgenum,vet[MAXM],val[MAXM],Next[MAXM],Head[MAXN];
    	void addedge(int u,int v){
    		vet[++edgenum]=v;
    		Next[edgenum]=Head[u];
    		Head[u]=edgenum;
    	}
    } using namespace Graph;
    namespace HLD{//Heavy-Light Decompositions
    	int fa[MAXN],sz[MAXN],son[MAXN];
    	int dtime,L[MAXN],R[MAXN],num[MAXN];
    	int tot[MAXN],top[MAXN];
    	void dfs(int u,int f){
    		fa[u]=f; sz[u]=1; son[u]=0;
    		for (int e=Head[u],v;e;e=Next[e]){ v=vet[e];
    			if (v==f) continue;
    			dfs(v,u); sz[u]+=sz[v];
    			if (sz[v]>sz[son[u]]) son[u]=v;
    		}
    	}
    	void dfs2(int u,int tp){
    		top[u]=tp; tot[tp]++; num[++dtime]=u;
    		L[u]=dtime;
    		if (son[u]) dfs2(son[u],tp);
    		for (int e=Head[u],v;e;e=Next[e]){ v=vet[e];
    			if (v==fa[u]||v==son[u]) continue;
    			dfs2(v,v);
    		}
    		R[u]=dtime;
    	}
    } using namespace HLD;
    
    namespace BIT{//Binary Index Tree
    	int tree[MAXN];
    	void add(int x,int y){
    		for (;x<=n;x+=x&-x) tree[x]+=y;
    	}
    	int query(int x){
    		int res=0;
    		for (;x;x-=x&-x) res+=tree[x];
    		return res;
    	}
    } using namespace BIT;
    
    struct List{//表头为权值,后面接着点的编号
    	int pre[MAXM],nxt[MAXM];
    	void del(int x){
    		if (nxt[x]) pre[nxt[x]]=pre[x];
    		if (pre[x]) nxt[pre[x]]=nxt[x];
    		pre[x]=x; nxt[x]=x;
    	}
    	void ins(int v,int x){//在权值v后插入点x
    		v+=n+1;
    		nxt[x]=nxt[v];
    		if (nxt[v]) pre[nxt[v]]=x;
    		nxt[v]=x; pre[x]=v;
    	}
    	int get(int v){ return nxt[v+n+1];}
    } Up,Dw;
    int up[MAXN],dw[MAXN],up_w[MAXN],dw_w[MAXN];
    void update(int x){//更新每条链的分界点的值,存入Up,Dw
    	Up.del(x);
    	if (up[x]>=1) Up.ins(up_w[x],x);
    	Dw.del(x);
    	if (dw[x]<=tot[x]) Dw.ins(dw_w[x],x);
    }
    /*
    	up[x]为链上最深度最大的A中的点的深度,up_w[x]为其w值
    	dw[x]为链上最深度最小的B中的点的深度,dw_w[x]为其w值
    	>lim的点放入A中,<=lim的点放入B中
    */
    void update_w(int x,int d,int w){//更新分界点的w值
    	if (1<=up[x]&&up[x]<=d) up_w[x]+=w;
    	if (dw[x]<=d) dw_w[x]+=w;
    }
    int get_w(int x){ return query(R[x])-query(L[x]-1);}//获得一个在S中的点的w值
    set<int> s[MAXN];
    set<int>::iterator it;
    int pred(int x,int y){
    	it=s[x].lower_bound(y);
    	if (it==s[x].begin()) return 0;
    	else return *--it;
    }
    int succ(int x,int y){
    	it=s[x].upper_bound(y);
    	if (it==s[x].end()) return 0;
    	else return *it;
    }
    void delA(int x){//将链上的深度最大的A从A中删除
    	up[x]=pred(x,up[x]);
    	up_w[x]=!up[x]?0:get_w(num[L[x]+up[x]-1]);
    }
    void delB(int x){//将链上的深度最大的B从B中删除
    	dw[x]=succ(x,dw[x]);
    	dw_w[x]=dw[x]>tot[x]?0:get_w(num[L[x]+dw[x]-1]);
    }
    void AtoB(int x){//将链上的深度最大的A移到B中
    	dw[x]=up[x]; dw_w[x]=up_w[x];
    	delA(x);
    }
    void BtoA(int x){//将链上的深度最小的B移到A中
    	up[x]=dw[x]; up_w[x]=dw_w[x];
    	delB(x);
    }
    int cnt,lim;
    void ins(int x){
    	add(L[x],1);
    	int t=top[x],d=L[x]-L[t]+1;//d为以t为根时x的深度(t深度为1)
    	s[t].insert(d);
    	update_w(t,d,1);//更新链上分界点的w(分界点在t--x的链上)
    	if (dw_w[t]>lim){ cnt++; BtoA(t);}//直接上移,因为最多分界点只会移动一步
    	else
    		if (dw[t]>d){//现在已知dw[t]满足条件且在x之下,看分界点是否能移动
    			int s=get_w(x);
    			if (s<=lim){ dw[t]=d; dw_w[t]=s;}//原先t--fa[x]链上所有点都在A中,son[x]--*所有点都在B中,且当前点可以加入B
    			else {
    				cnt++;
    				if (d>up[t]){ up[t]=d; up_w[t]=s;}//原先t--fa[x]链上所有点都在A中,son[x]--*所有点都在B中,且当前点可以加入A
    			}
    		}
    	update(t);
    	for (int x=fa[t],t=top[x];x;x=fa[t],t=top[x]){
    		d=L[x]-L[t]+1;
    		update_w(t,d,1);//更新链上分界点的w
    		if (dw_w[t]>lim){ cnt++; BtoA(t);}//分界点上移
    		update(t);
    	}
    	//前面的操作会导致cnt增大
    	while (cnt>lim){
    		int x=Up.get(lim);
    		if (!x){ lim++; continue;}//没有权值为lim的A中的点
    		cnt--; AtoB(x); update(x);
    	}
    	//做完后cnt==lim
    }
    void del(int x){
    	add(L[x],-1);
    	int t=top[x],d=L[x]-L[t]+1;//d为以t为根时x的深度(t深度为1)
    	s[t].erase(d);
    	update_w(t,d,-1);//更新链上分界点的w(分界点在t--x的链上)
    	if (d<=up[t]) cnt--;//从A中删除,因为x已经不在S中了
    	if (d==up[t]) delA(t);//若x为A中深度最大的点,删去后要重新求深度最大的点
    	else if (d==dw[t]) delB(t);//若x为B中深度最大的点,删去后要重新求深度最大的点
    	if (up[t]>=1&&up_w[t]<=lim){ cnt--; AtoB(t);}//x到根路径所有w--,可能导致分界点下移
    	update(t);
    	for (int x=fa[t],t=top[x];x;x=fa[t],t=top[x]){
    		d=L[x]-L[t]+1;
    		update_w(t,d,-1);//更新链上分界点的w
    		if (up[t]>=1&&up_w[t]<=lim){ cnt--; AtoB(t);}//分界点下移
    		update(t);
    	}
    	//前面的操作会导致cnt减小
    	while (cnt<lim){
    		int x=Dw.get(lim);
    		if (!x){ lim--; continue;}//没有权值为lim的A中的点
    		cnt++; BtoA(x); update(x);
    	}
    }
    int main(){
    //	File("tree");
    	n=read();
    	for (int i=1;i<=(n<<1|1);i++){
    		Up.pre[i]=0; Up.nxt[i]=0;
    		Dw.pre[i]=0; Dw.nxt[i]=0;
    	}
    	int t,u,v;
    	for (int i=1;i<n;i++){
    		u=read(); v=read();
    		addedge(u,v); addedge(v,u);
    	}
    	dfs(1,0); dfs2(1,1);
    	for (int i=1;i<=n;i++)
    		if (top[i]==i){
    			dw[i]=tot[i]+1;//B中深度最小的点是i往下dw[i]深度的点
    			s[i].insert(0); s[i].insert(tot[i]+1);
    		}
    	q=read(); 
    	while (q--){
    		t=read(); v=read();
    		if (t==1) ins(v);
    		else del(v);
    		write(cnt); pc('\n'); 
    	}
    	IOflush();
    	return 0;
    }
    /*
    5
    1 2
    1 3
    1 4
    2 5
    6
    1 4
    1 1
    1 2
    1 5
    2 2
    1 3
    */
    
  • 相关阅读:
    14-定时器
    13-JS中的面向对象
    12-关于DOM操作的相关案例
    11-DOM介绍
    10-关于DOM的事件操作
    09-伪数组 arguments
    08-函数
    07-常用内置对象
    06-流程控制
    05-数据类型转换
  • 原文地址:https://www.cnblogs.com/hydd-233/p/13504049.html
Copyright © 2020-2023  润新知