• AGC004_F Namori


    题面翻译

    给你一棵树/基环树,初始所有点为白色。每次可以选择相邻的两个同色点,使它们的颜色反转(白->黑,黑->白)。问最终能否将所有点变成黑点,如能输出最少操作次数,否则输出-1。

    思路
    这题得分三种情况讨论……
    首先最简单的是树。
    我们考虑把题面转换一下。我们选一个点为根,然后所有深度为奇数/偶数的点上标+1,深度为偶数/奇数的点上标-1。每次操作将交换相邻的+1与-1。问最少移动多少次可以使每个深度为偶数/奇数的点都有棋子,而每个深度为奇数/偶数的点上都没有棋子。显然这个这个转换时没有问题的,那么如果深度为奇数的点个数等于为偶的,则可行。因为对于每次操作可以说是独立的,所以我们想让操作次数最少,使所有棋子移动距离最小,就要尽量让所有棋子在子树内移动。我们设(S_x)表示x的子树内深度为奇的点个数-深度为偶的点个数。如果(S_x>0),表示子树内总有棋子需要移出子树。如果(S_x<0),表示总有棋子需要移入子树内。所以答案为(sum_{i=1}^{n}|S_i|)
    然后我们考虑一下环的长度为奇数的基环树。
    我们考虑去掉环上的一条边,这样就变成了一棵树,但是去掉的这条边连接了两个奇偶性相同的点,这意味着多出的这条边可以在两边同时产生+1或-1。那么如果整棵树的点个数部位偶数意味着无解。同时我们只会在多出的边两端同时产生+1或-1,否则相互抵消就显得多余。那么这样是会产生还是删掉并且变化量为多少就显然了(根据整棵树奇数点个数与偶数点个数的差值)。而且每次操作相互独立,我们可以在多出的边上先进行那么多操作,再算答案。
    最后是环的长度为偶数的基环树。
    同样删掉一条边。但此时这条边两端奇偶相异。那么这条边的作用就如同“地下通道”,可以代替两点间每条边依次交换的操作。假设这条边两端点为x和y,lca为x和y的最近公共祖先,我们通过这条边向y运输了z个+1,那么对于x到lca(不包括lca)这一路上的点的(S[])值都要-z,y到lca(不包括lca)这一路上的点的(S[])值都要+z。那么当x去何值是最优呢?我们不看不会变的(S[])值,那么受影响的值对答案的贡献为(egin{cases}|S_u-z|&,uin x->lca\|S_u+z|&,uin y->lcaend{cases})(还要加上操作次数z)。
    如果我们把y这一边的(S[])值都乘上-1,那么贡献就改为((sum |S_u-z|(uin x->y))+z)。我们把(z)看成(|0-z|),然后就变成经典的库仓选址问题,直接取z=中位数即可。

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int maxn=1e5;
    int n,m,tot=1,flag,key,cnt;
    int pre[maxn*2+8],now[maxn+8],son[maxn*2+8];
    int dep[maxn+8],fa[maxn+8],f[maxn+8],v[maxn+8],tmp[maxn+8];
    
    int read()
    {
        int x=0,f=1;char ch=getchar();
        for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
        for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
        return x*f;
    }
    
    void add(int u,int v)
    {
        pre[++tot]=now[u];
        now[u]=tot;
        son[tot]=v;
    }
    
    void check(int x)
    {
        dep[x]=dep[fa[x]]+1;
        v[x]=(dep[x]&1)*2-1;
        f[x]=v[x];
        for (int p=now[x];p;p=pre[p])
    	{
    	    int child=son[p];
    	    if (fa[x]==child) continue;
    	    if (fa[child]) {flag=((dep[x]-dep[child]+1)&1)+1,key=p;continue;}
    	    fa[child]=x;
    	    check(child);
    	    f[x]+=f[child];
    	}
    }
    
    void dfs(int x)
    {
        f[x]=v[x];
        for (int p=now[x];p;p=pre[p])
    	{
    	    int child=son[p];
    	    if ((p|1)==(key|1)||fa[x]==child) continue;
    	    dfs(child);
    	    f[x]+=f[child];
    	}
    }
    
    int main()
    {
        n=read(),m=read();
        for (int i=1;i<=m;i++)
    	{
    	    int u=read(),v=read();
    	    add(u,v),add(v,u);
    	}
        fa[1]=1;
        check(1);
        if (!flag)
    	{
    	    if (f[1])
    		{
    		    puts("-1");
    		    return 0;
    		}
    	    ll ans=0;
    	    for (int i=1;i<=n;i++) ans+=abs(f[i]);
    	    printf("%lld
    ",ans);
    	}
        if (flag==2)
    	{
    	    if (f[1]&1)
    		{
    		    puts("-1");
    		    return 0;
    		}
    	    //for (int i=1;i<=n;i++) printf("%d ",dep[i]);puts("");
    	    //printf("check:%d %d %d
    ",f[1],son[key],son[key^1]);
    	    //printf("%d %d
    ",v[son[key]],v[son[key^1]]);
    	    v[son[key]]-=f[1]/2;
    	    v[son[key^1]]-=f[1]/2;
    	    ll ans=abs(f[1]/2);
    	    //printf("%d %d
    ",v[son[key]],v[son[key^1]]);
    	    dfs(1);
    	    //printf("check:%d
    ",f[1]);
    	    for (int i=1;i<=n;i++) ans+=abs(f[i]);
    	    printf("%lld
    ",ans);
    	}
        if (flag==1)
    	{
    	    if (f[1])
    		{
    		    puts("-1");
    		    return 0;
    		}
    	    if (dep[son[key]]<dep[son[key^1]]) key^=1;
    	    int x=son[key];
    	    tmp[++cnt]=0;
    	    while(x!=son[key^1]) tmp[++cnt]=f[x],x=fa[x];
    	    sort(tmp+1,tmp+cnt+1);
    	    ll ans=tmp[(cnt+1)/2];
    	    v[son[key]]-=ans;
    	    v[son[key^1]]+=ans;
    	    dfs(1);
    	    ans=abs(ans);
    	    for (int i=1;i<=n;i++) ans+=abs(f[i]);
    	    printf("%lld
    ",ans);
    	}
        return 0;
    }
    
  • 相关阅读:
    网卡驱动-BD详解(缓存描述符 Buffer Description)
    break&&continue
    C++解析六-继承
    C++解析五-this 指针,指向类的指针
    1-find
    C++解析四-友员函数、内联函数、静态成员
    树形结构节点求和,以及set排序
    java.util.ConcurrentModificationException 解决和for循环时对集合remove操作
    eclipse保存卡死和内存溢出
    关于Eclipse安装了反编译插件,无法查看源码问题
  • 原文地址:https://www.cnblogs.com/Alseo_Roplyer/p/10174948.html
Copyright © 2020-2023  润新知