其他分享
首页 > 其他分享> > P6805-[CEOI2020]春季大扫除【贪心,树链剖分,线段树】

P6805-[CEOI2020]春季大扫除【贪心,树链剖分,线段树】

作者:互联网

正题

题目链接:https://www.luogu.com.cn/problem/P6805


题目大意

给出\(n\)个点的一棵树,\(q\)次独立的询问。每次询问会在一些节点上新增一些子节点,然后你每次可以选择两个为选择过的叶子节点然后覆盖它们的路径,要求在覆盖所有边的情况下使得每次的路径长度和最小。

\(1\leq n,q,\sum d_i\leq 10^5\)


解题思路

先考虑暴力怎么做,我们可以把所有叶子去掉然后每个点的权值就是它原来子节点中的叶子数。

然后由于一个节点之间的权值可以两两匹配,贪心的话一个节点只有可能有\(1/2\)条路径延伸到父节点,这样就可以统计了。

然后考虑多个询问如何处理,根据上面的方法,每条边只有可能统计\(1/2\)次,而统计两次时当且仅当子树内能够两两配对,此时因为不能有边没有覆盖,所以就只能拆开一个配对分两个上来。

具体地当一个点的子树中叶子数为偶数时它到其父节点的边会被统计两次。

这个用树链剖分维护即可。

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


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int N=1e5+10;
struct node{
	int to,next;
}a[N<<1];
int n,m,tot,cnt,ls[N],leaf[N],lsz[N];
int dep[N],fa[N],siz[N],son[N],top[N],id[N];
int w[N<<2],v[N<<2],lazy[N<<2];stack<int> s;
void Downdata(int x){
	if(!lazy[x])return;
	lazy[x*2]^=1;lazy[x*2+1]^=1;
	swap(w[x*2],v[x*2]);
	swap(w[x*2+1],v[x*2+1]);
	lazy[x]=0;return;
}
void Build(int x,int L,int R){
	if(L==R){w[x]=(L>1);return;}
	int mid=(L+R)>>1;
	Build(x*2,L,mid);Build(x*2+1,mid+1,R);
	w[x]=w[x*2]+w[x*2+1];
}
void Change(int x,int L,int R,int l,int r){
	if(L==l&&R==r){swap(w[x],v[x]);lazy[x]^=1;return;}
	int mid=(L+R)>>1;Downdata(x);
	if(r<=mid)Change(x*2,L,mid,l,r);
	else if(l>mid) Change(x*2+1,mid+1,R,l,r);
	else Change(x*2,L,mid,l,mid),Change(x*2+1,mid+1,R,mid+1,r);
	w[x]=w[x*2]+w[x*2+1];v[x]=v[x*2]+v[x*2+1];return;
}
void addl(int x,int y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;return;
}
void dfs(int x){
	leaf[x]=(a[ls[x]].next==0);
	siz[x]=1;dep[x]=dep[fa[x]]+1;
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		if(y==fa[x])continue;
		fa[y]=x;dfs(y);
		lsz[x]+=lsz[y];siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])son[x]=y;
	}
	lsz[x]+=leaf[x];
	return;
}
void dfs2(int x){
	id[x]=++cnt;
	if(lsz[x]&1)Change(1,1,n,cnt,cnt); 
	if(son[x]){
		top[son[x]]=top[x];
		dfs2(son[x]);
	}
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		if(y==fa[x]||y==son[x])continue;
		top[y]=y;dfs2(y);
	}
	return;
}
void Updata(int x){
	while(x){
		Change(1,1,n,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	return;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		addl(x,y);addl(y,x);
	}
	Build(1,1,n);dfs(1);
	top[1]=1;dfs2(1);
	while(m--){
		int k,x,sum=lsz[1];
		scanf("%d",&k);
		for(int i=1;i<=k;i++){
			scanf("%d",&x);
			if(leaf[x]==1)leaf[x]=2;
			else sum++,Updata(x);
			s.push(x);
		}
		if(sum&1)puts("-1");
		else printf("%d\n",n-1+k+w[1]);
		while(!s.empty()){
			int x=s.top();
			if(leaf[x]==2)leaf[x]=1;
			else Updata(x);s.pop();
		} 
	}
	return 0;
}

标签:return,剖分,int,void,mid,树链,CEOI2020,节点,Change
来源: https://www.cnblogs.com/QuantAsk/p/15433139.html