[agc004F]Namori

Description

传送门

Solution

以下内容转载自大佬的博客,链接为https://blog.csdn.net/werkeytom_ftd/article/details/78393489。

----------------------------------------------------------------------------------------------------------

 

树的做法

 

树是一个二分图,看起来很棒的样子。 
我们不妨设深度为奇数(根的深度为1)的点是一个空位,而深度为偶数的点有一个硬币。 
我们发现,一次操作相当于将一个硬币移到相邻的空位。 
最终要求原本是空位的点都被硬币填满。 
那么只有硬币数等于空位数才有解。 
不妨记空位为1,硬币为-1。 
si表示子树和,最小的答案是ni=1abs(si)∑i=1nabs(si) 
因为这个是一定要从这个子树运出的硬币数或运进的硬币数,这个值代表其和父亲边的操作次数。这个式子显然是下界。 
而不难发现该下界可以达到。对于一个点,我们把它的儿子分为运进儿子和运出儿子,每次把运出儿子的一个硬币匹配到运进儿子,当然可行。 
这个做法很重要,接下来我们来解决环套树,分两种情况讨论。

 

奇环

 

任意断开环上一边u和v,变成树。 
u和v一定属于二分图同一侧,我们操作一次u-v,显然是在u和v各增加一个硬币或各减少一个硬币。 
不妨计算出变成树后所多余或所缺少的硬币数量,如果是奇数则无解,否则均摊到u和v上。 
然后像树的做法做一遍即可。

 

偶环

 

同样任意断开一边u和v。 
然后设u-v这条边操作了x次(可以是负数),那么u上增加x个硬币,v上则减少x个硬币。 
设ki表示子树i最终带x的系数,显然只有0、1、-1。 
那么答案是abs(x)+ni=1abs(kix+si)abs(x)+∑i=1nabs(kix+si) 
k为0的可以直接加进答案,剩下的可以看做数轴上若干点,选一个点使得这些点到该点距离和最小,是经典问题,取中位数最优。

----------------------------------------------------------------------------------------------------------

PS:这里的所谓硬币的定义,应该是当硬币在深度为偶数的点上,该点为白色;硬币在深度为奇数的点上,该点为黑色。应该是这个意思吧qaq

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,x,y;
struct node{int y,nxt;
}g[200010];int tot=0,h[100010];
bool vis[100010];
int cirx=0,ciry=0;bool is_single;
int dep[100010],sum[100010],f[100010],ans=0;
void dfs(int x,int fa)
{
    f[x]=fa;
    vis[x]=1;dep[x]=dep[fa]^1;sum[x]=dep[x]&1?-1:1;
    for (int i=h[x];i;i=g[i].nxt)
    if (g[i].y!=fa){
        if (vis[g[i].y]) 
        {
            cirx=g[i].y;ciry=x;is_single=(dep[x]==dep[g[i].y]);
        } else
        dfs(g[i].y,x),sum[x]+=sum[g[i].y];
    }
    ans+=abs(sum[x]);
}

int k[100010];//not single circle
int p[100010],cntp;
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        g[++tot]=node{y,h[x]};h[x]=tot;
        g[++tot]=node{x,h[y]};h[y]=tot;
    }
    dfs(1,0);
    if (m==n)
    {
        if (is_single)
        {
            if (sum[1]&1) {printf("-1");return 0;}
            int t=-sum[1]/2;
            for (;cirx;cirx=f[cirx]) sum[cirx]+=t;
            for(;ciry;ciry=f[ciry]) sum[ciry]+=t;
            ans=abs(t);
            for (int i=1;i<=n;i++) ans+=abs(sum[i]);
            printf("%d",ans);
        } else
        {
            if (sum[1]){printf("-1");return 0;}
            for(;cirx;cirx=f[cirx]) k[cirx]++;
            for(;ciry;ciry=f[ciry]) k[ciry]--;
            p[cntp=1]=0;ans=0;
            for (int i=1;i<=n;i++) 
            if (!k[i]) ans+=abs(sum[i]);else p[++cntp]=k[i]*sum[i];
            sort(p+1,p+cntp+1);
            int t=p[1+cntp>>1];
            for (int i=1;i<=cntp;i++) ans+=abs(p[i]-t);
            printf("%d",ans);
        } 
        return 0;
    }
    if (!sum[1]) printf("%d",ans);else printf("-1");
}

 

 

posted @ 2018-08-20 17:00  _雨后阳光  阅读(121)  评论(0编辑  收藏  举报