zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    HDU-1754 I Hate It (树状数组模板题——单点更新,区间查询最大值)
    HDU-1166 敌兵布阵 (树状数组模板题——单点更新,区间求和)
    JavaScript dotAll模式
    ECMAScript6补全字符串长度方法padStart()和padEnd()
    ECMAScript6重复字符串方法repeat()
    JavaScript确定一个字符串是否包含在另一个字符串中的四种方法
    JavaScript中label与break配合使用
    JavaScript数据结构与算法-集合练习
    JavaScript数据结构与算法-散列练习
    JavaScript数据结构与算法-字典练习
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/15433139.html
Copyright © 2011-2022 走看看