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[]\)值,那么受影响的值对答案的贡献为\(\begin{cases}|S_u-z|&,u\in x->lca\\|S_u+z|&,u\in y->lca\end{cases}\)(还要加上操作次数z)。
如果我们把y这一边的\(S[]\)值都乘上-1,那么贡献就改为\((\sum |S_u-z|(u\in 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\n",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\n",f[1],son[key],son[key^1]);
	    //printf("%d %d\n",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\n",v[son[key]],v[son[key^1]]);
	    dfs(1);
	    //printf("check:%d\n",f[1]);
	    for (int i=1;i<=n;i++) ans+=abs(f[i]);
	    printf("%lld\n",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\n",ans);
	}
    return 0;
}
posted @ 2018-12-25 16:45  Alseo_Roplyer  阅读(250)  评论(0编辑  收藏  举报