acm-(好题、树链剖分、数据结构)CF1403B Spring cleaning

题面
传送门
容易发现,对于一颗确定的树而言,找到一个非叶子节点作为根节点,然后给每条边 e d g e i , j ( d e p [ j ] > d e p [ i ] ) edge_{i,j}(dep[j]>dep[i]) edgei,j(dep[j]>dep[i])赋一个权值 w i , j w_{i,j} wi,j,设 c n t i cnt_i cnti表示以 i i i为根节点的子树中的叶子节点个数,若 c n t j % 2 = 1 cnt_j\%2=1 cntj%2=1,那么 w i , j = 1 w_{i,j}=1 wi,j=1,若 c n t j % 2 = = 0 cnt_{j}\%2==0 cntj%2==0,那么 w i , j = 2 w_{i,j}=2 wi,j=2,答案树上的所有边权之和。

先考虑无解情况,显然当叶子结点有奇数个的时候无解,其它情况均有解,容易知道当前叶子结点的个数的奇偶性。

现在考虑在树上的某些节点的上加一些叶子节点,有两种情况,一种是在非叶子节点上加叶子节点,那么从该非叶子节点到根节点的路径上的所有边权都会反转(即1变成2,2变成1),此外还多了一条边。第二种是在叶子节点上加一个叶子节点,与第一种的区别在于第一次加的时候不会发生到根节点路径上的边权反转,仅仅只是多了一条边。

知道上面这些后就不难得出本题的解法了,考虑树链剖分维护树上的区间和,加节点的操作其实就是区间的反转操作,打对应翻转懒标记即可,然后第二种情况需要特判一下才行。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5+5;

int fa[maxn],id[maxn],rk[maxn],top[maxn],son[maxn],tot=0,sz[maxn],
    cnt[maxn],val[maxn],val2[maxn],t[maxn<<2],lz[maxn<<2],vis[maxn];
vector<int>g[maxn];

void dfs1(int u,int f){
    fa[u]=f;
    sz[u]=1;
    cnt[u]=1;
    bool fg=false;
    for(int i=0;i<g[u].size();++i){
        int v=g[u][i];
        if(v==f)continue;
        fg=true;
        dfs1(v,u);
        if(cnt[v]&1)val[v]=1;else val[v]=2;
        sz[u]+=sz[v];
        cnt[u]+=cnt[v];
        if(sz[v]>sz[son[u]]){
            son[u]=v;
        }
    }
    cnt[u]-=fg;
}
void dfs2(int u,int f){
    id[u]=++tot;
    val2[tot]=val[u];
    rk[tot]=u;
    top[u]=f;
    if(!son[u])return;
    dfs2(son[u],f);
    for(int i=0;i<g[u].size();++i){
        int v=g[u][i];
        if(v==fa[u] || v==son[u])continue;
        dfs2(v,v);
    }
}

void pushup(int rt){
    t[rt]=t[rt<<1]+t[rt<<1|1];
}
void pushdown(int rt,int l,int r){
    if(lz[rt]){
        int mid=l+r>>1;
        lz[rt<<1]^=1;lz[rt<<1|1]^=1;
        t[rt<<1]=3*(mid-l+1)-t[rt<<1];
        t[rt<<1|1]=3*(r-mid)-t[rt<<1|1];
        lz[rt]=0;
    }
}
void build(int rt,int l,int r){
    if(l==r){
        t[rt]=val2[l];
        return;
    }
    int mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    pushup(rt);
}
void cg(int rt,int l,int r,int ql,int qr){
    if(ql<=l && r<=qr){
        lz[rt]^=1;
        t[rt]=3*(r-l+1)-t[rt];
        return;
    }
    pushdown(rt,l,r);
    int mid=l+r>>1;
    if(ql<=mid && qr>=l)cg(rt<<1,l,mid,ql,qr);
    if(ql<=r && qr>=mid+1)cg(rt<<1|1,mid+1,r,ql,qr);
    pushup(rt);
}
vector<int>rec;

void work(int u,int root){
    while(top[u]!=root){
        cg(1,1,tot,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if(u!=root){
        cg(1,1,tot,id[son[root]],id[u]);
    }
}
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    int root=-1;
    for(int i=0;i<n-1;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        g[u].push_back(v);
        g[v].push_back(u);
        if(g[u].size()>1)root=u;
        if(g[v].size()>1)root=v;
    }
    dfs1(root,0);
    dfs2(root,root);
    build(1,1,tot);
    while(q--){
        int num=0;
        scanf("%d",&num);
        int ct=0;
        rec.clear();
        for(int i=0;i<num;++i){
            int u;
            scanf("%d",&u);
            rec.push_back(u);
            if((int)g[u].size()==1 && !vis[u])ct++;
            vis[u]=1;
        }
        if((cnt[root]+num-ct)&1){
            printf("-1\n");
            for(int i=0;i<rec.size();++i)vis[rec[i]]=0;
            continue;
        }
        for(int i=0;i<rec.size();++i)vis[rec[i]]=0;
        for(int i=0;i<rec.size();++i){
            if((int)g[rec[i]].size()==1 && !vis[rec[i]]){
                vis[rec[i]]=1;
                continue;
            }
            work(rec[i],root);
        }
        int ans=t[1]+num;
        for(int i=0;i<rec.size();++i)vis[rec[i]]=0;
        for(int i=0;i<rec.size();++i){
            if((int)g[rec[i]].size()==1 && !vis[rec[i]]){
                vis[rec[i]]=1;
                continue;
            }
            work(rec[i],root);
        }
        for(int i=0;i<rec.size();++i)vis[rec[i]]=0;
        printf("%d\n",ans);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值