洛谷AT2046 Namori(思惟,基環樹,樹形DP)

洛谷題目傳送門c++

神仙思惟題仍是要寫點東西纔好。數組

每次操做把相鄰且同色的點反色,直接這樣思考會發現狀態有很強的後效性,沒辦法考慮轉移。spa

由於樹是二分圖,因此咱們轉化模型:在樹的奇數層的全部點上都有一枚棋子,每次能夠將棋子移向相鄰的空位,目標狀態是樹的偶數層的全部點上都有棋子。code

這樣的互換總次數有沒有一個下界呢?排序

咱們求出\(a_i\)表示點\(i\)子樹中棋子數量與空位數量之差(能夠是負數),那麼\(i\)的父邊就至少要交換\(|a_i|\)次。ip

爲何呢?子樹裏面空位比棋子少的話,確定要經過父邊把\(a_i\)個棋子送出去,才能移進來\(a_i\)個空位。反之亦然。get

因而,若是\(a_{rt}\)(根)不爲\(0\)就無解,不然\(\sum\limits_{i=1}^n|a_i|\)就是答案下界。it

而後,仔細推一下發現它就是答案。class

對於一個點,它的父邊和全部子邊的移進移出的順序,和其它點的順序是互相獨立的。im

等於說咱們總能安排一個合法的順序,使得它們一進一出一進一出。。。這樣完整地銜接起來,而不會互相矛盾。

基環樹

環長爲偶數

顯然的想法:把整個環當作根,不在環上的點的答案照算不誤。因此把多出來一條邊去掉作好樹形DP。

首先這仍是一個二分圖,所以\(a_{rt}\)不爲\(0\)一樣無解。

因而,彷佛這條邊存在的意義只有分攤一部分轉移、減小總次數了。

那麼接下來要作的是,肯定環上每條邊的移動方向和次數。

設環長爲\(n\)\(A_i\)爲以\(i\)爲根求得的\(a\)值,\(x_i\)爲環上第\(i\)條邊的移動參數(正負表示方向,絕對值表示次數)

由於進出平衡,咱們能夠獲得一個方程組

\[\begin{cases}x_1-x_2=A_1\\x_2-x_3=A_2\\...\\x_n-x_1=A_n\end{cases}\]

這時候咱們發現上面那個方程組是沒有惟一解的。那到底該分攤多少呢?

不着急,咱們着眼於最小化\(\sum|x_i|\),再把方程組整一整

\[\begin{cases}x_1=x_1-0\\x_2=x_1-A_1\\x_3=x_1-A_1-A_2\\...\\x_n=x_1-\sum_{i=1}^{n-1}A_i\end{cases}\]

每一行的\(A\)都是前綴和的形式,在樹形DP的時候對應的是環上的每一個點的\(a\)

看到這兒就豁然開朗了。這不等於說,數軸上有若干個點取值分別爲每個\(a\),找到一個點\(x_1\)使它到全部點距離之和最小?

把全部\(a\)排序,\(x_1\)不就能夠取第\(\frac n 2\)大的數到第\(\frac n 2+1\)大的數之間的任意整數麼?

最後把環上的DP值改一改就OK了。

環長爲奇數

由於不是二分圖了,因此直接解方程好像作不下去。咱們從更直觀的意義來理解它。

仍然是去掉一條邊作樹形DP。這時候咱們看看,在這條非樹邊上操做,在奇偶染色模型下等價於什麼呢?

沒錯,兩個棋子會在這裏同時變成空位,兩個空位會在這裏同時變成棋子!

那這條邊存在的意義,是把棋子或空位中多的減掉、少的補上。要操做多少次呢?\(\frac{\sum A_i}{2}\),在樹形DP中對應的是\(\frac{a_{rt}}{2}\)

注意若是\(a_{rt}\)是奇數那麼無解。不然環底部的\(A\)發生了變化,致使要把環上的\(a\)都減掉\(\frac{a_{rt}}{2}\)


最後的最後掃一遍數組統計答案。代碼沒有任何難點。

#include<bits/stdc++.h>
#define R register int
#define G if(++ip==ie)if(fread(ip=buf,1,SZ,stdin))
using namespace std;
const int SZ=1<<19,N=1e5+9,M=2*N;
char buf[SZ],*ie=buf+SZ,*ip=ie-1;
inline int in(){
    G;while(*ip<'-')G;
    R x=*ip&15;G;
    while(*ip>'-'){x*=10;x+=*ip&15;G;}
    return x;
}
int he[N],ne[M],to[M],f[N],a[N],b[N];
int Getf(R x){
    return x==f[x]?x:f[x]=Getf(f[x]);
}
void Dfs(R x,R op){
    a[x]=op;
    for(R y,i=he[x];i;i=ne[i])
        if((y=to[i])!=f[x])
            f[y]=x,Dfs(y,-op),a[x]+=a[y];
}
int main(){
    R n=in(),m=in(),rt=1,re=1,p=0,ans=0;
    for(R i=1;i<=n;++i)f[i]=i;
    for(R p=0,i=1;i<=m;++i){
        R x=in(),y=in();
        if(Getf(x)!=Getf(y))f[f[x]]=f[y];
        else{rt=x,re=y;continue;}//把多的邊單獨拿出來
        ne[++p]=he[x];to[he[x]=p]=y;
        ne[++p]=he[y];to[he[y]=p]=x;
    }
    f[rt]=0;Dfs(rt,1);
    if(n==m){//基環樹
        for(R x=re;x;x=f[x])b[++p]=a[x];
        if(p&1){//奇環
            if(a[rt]&1)return puts("-1"),0;
            for(R x=re;x;x=f[x])a[x]-=a[rt]>>1;
        }
        else{//偶環
            if(a[rt])return puts("-1"),0;
            sort(b+1,b+p+1);
            for(R x=re;x;x=f[x])a[x]-=b[p>>1];
        }
    }//樹
    else if(a[rt])return puts("-1"),0;
    for(R i=1;i<=n;++i)ans+=abs(a[i]);
    return cout<<ans<<endl,0;
}
相關文章
相關標籤/搜索