1. 程式人生 > >AGC004_F Namori

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;
}