2015年6月15日 星期一

[CF 468D] Tree

作法:

首先先觀察最佳解的排列會滿足什麼特性。現在等於是樹上有$n$條路徑,分別是$i$走到$p[i]$。如果兩條路徑$(i,p[i])$和$(j,p[j])$沒有公共點的話,那麼把$p[i],p[j]$交換會讓答案更大,因此就可以得到在最佳解時任兩條路徑都有公共點。接下來證明若在一棵樹上有很多條路徑,並且任兩條路徑都有公共點,那麼所有路徑有一個公共點。記這些路徑為$P_1,...,P_k$,考慮$P_1$和其他路徑的交集部份,他們都是由$P_1$上的某一段連續的點組成的(所以可以看成一個區間),並且易證明任兩個區間都有交集,否則原圖中會出現圈,矛盾。那麼這個命題就變得顯然了,因為這樣的$n$個區間必定會有一個公共點。

再來我們想知道有哪些點有資格被當作所有路徑都經過的點。對於某個點$P$來說,考慮$P$的最大的子樹,如果這棵子樹的大小$>\left \lfloor \frac{n}{2} \right \rfloor$,那麼顯然$P$不能被當作剛才所說的公共點,因為在那棵子樹裡的點都必須連向不在那棵子樹裡的點,但那棵子樹裡的點太多了,所以矛盾。因此我們得到了公共點必須滿足「把他拔掉後剩下的所有子樹大小都$\leq \left \lfloor \frac{n}{2} \right \rfloor$」。並且不難證明這是充要條件,任意構造一個就可以了,在這裡省略。有了這個條件後就不難想到取重心應該是對的,而事實上也只有重心會滿足這個條件,證明就補在這篇的最後面。

因此就可以得到第一個答案是所有點到重心的距離和的兩倍,麻煩的是要求出字典序最小的解。首先以重心為根建成有根樹,我們把題目轉成二分圖完美匹配來看,對於$i$指向$p[i]$,可以把他看成「$i$的出點」連向了「$p[i]$的入點」,所以如果把每個點都拆成出點和入點,並且若$i,j$在不同的重心的子樹裡,那麼在$i$的出點和$j$的入點之間連一條邊,這樣就變成了求這張圖的字典序最小的完美匹配了。首先考慮這樣的算法:當要決定$p[i]$為多少時,就從$1$開始往上枚舉,找出第一個「$i$的出點連了他的入點之後整張圖還存在完美匹配」的點,並且他還沒被別人選過,那麼$p[i]$就等於他。但問題是要知道存在完美匹配的條件,才能從剩餘的候選人裡選出值最小的拿來當$p[i]$。記重心的所有子樹們為$T_1,...,T_r$,對於某個瞬間來說,假設$T_i$的出點數量為$a_i$,入點數量則為$b_i$(因此在一開始$a_i=b_i=T_i$的大小),那麼可以列出一條蠻顯然的式子:$a_i\leq b_1+...+b_{i-1}+b_{i+1}+...+b_r$,因為$T_i$中的剩下的出點必須去找$T_i$以外的子樹的入點來連,所以顯然個數要比候選人少。而由 Hall 定理(二分圖存在完美匹配的充要條件)可以得到這件事就是充要條件了。將上式改寫成$a_i+b_i\leq b_1+...+b_r$,並且易知$b_1+...+b_r$等於還沒匹配的點的對數,也就是我們得到了在任一瞬間,對於所有的$i$都必須滿足$a_i+b_i\leq $還沒匹配的點的對數。因此就得到了這樣的算法:當要計算$p[i]$時,先看看有沒有哪棵子樹的$a+b$的值已經等於還沒匹配的點的對數了(這裡還沒匹配的點的對數其實就等於$n-i+1$),如果有的話那麼$p[i]$就必須要從那棵子樹裡取,那麼就取那棵子樹中值最小的點即可。如果沒有這樣的子樹的話,就可以取$i$所在的子樹以外的所有點,因此也取這些點中的最小的點即可。實作上可以對每棵子樹紀錄一個 set ,代表還有哪些入點還沒用,還有一個 set 裡面放了每棵子樹的入點的最小值,根一個 set<pair<int,int>> ,其中 pair 的第一個值為某棵子樹的$a+b$值,第二個則為這是第幾棵子樹,維護好這些東西就好了。另外還要特判根,也就是要記得判$i$是根的情形,還有有可能取的$p[i]$是根的情形。

最後,以下證明前面提到的「一個點是重心若且唯若可以被當作公共點」。

設$P$為重心,若$P$不可被當作公共點,設$T_1$是$P$的大小$> \left \lfloor \frac{n}{2} \right \rfloor$的子樹,其根為$Q$,那麼把$Q$拔掉的話,包含$P$的那棵子樹的大小就會$<n-\left \lfloor \frac{n}{2} \right \rfloor=\left \lceil \frac{n}{2} \right \rceil$$\leq  \left \lfloor \frac{n}{2} \right \rfloor+1\leq T_1$的大小,並且把$Q$拔掉後的其他子樹大小顯然都會小於$T_1$的大小,因此把$Q$拔掉會讓剩餘的最大子樹的大小比把$P$拔掉的還要小,則$P$不是重心,矛盾。因此重心可以被當作公共點。

再來證明非重心的點不能被當作公共點,一樣設$P$為重心,並且$Q$不是重心,他落在$P$的$T_1$子樹中,那麼有$T_1$的大小$\leq \left \lfloor \frac{n}{2} \right \rfloor$,因此整棵樹扣掉$T_1$的大小$\geq \left \lceil \frac{n}{2} \right \rceil$,因此可以得到若$Q$想要當公共點,他就必須是$T_1$的根,並且滿足$T_1$的大小剛好是$\left \lfloor \frac{n}{2} \right \rfloor$,並且整棵樹扣掉$T_1$的大小要剛好是$\left \lceil \frac{n}{2} \right \rceil$,所以就可以得到$n$為偶數,$T_1$的大小為$\frac{n}{2}$,所以$Q$也是重心,矛盾。

code :



#include<bits/stdc++.h>
#define LL long long
#define pii pair<int,int>
#define F first
#define S second
using namespace std;
const int maxn=100000+10 ;
 
struct P{int to,dis;};
vector<P> v[maxn] ;
int n ;
LL d[maxn] ;
int dfs(int x,int f,int &id,int &mi)
{
    int sz=1,maxs=0 ;
    for(auto i : v[x]) if(i.to!=f)
    {
        d[i.to]=d[x]+i.dis ;
        int tmp=dfs(i.to,x,id,mi) ;
        sz+=tmp ; maxs=max(maxs,tmp) ;
    }
    int val=max(maxs,n-sz) ;
    if(val<mi) mi=val , id=x ;
    return sz ;
}
void dfs(int x)
{
    int id,mi ;
    dfs(x,-1,id,mi) ; /// calculate dis
}
 
void dfs2(int x,int f,set<int> &s)
{
    s.insert(x) ;
    for(auto i : v[x]) if(i.to!=f)
        dfs2(i.to,x,s) ;
}
 
int cent ; /// centroid, also root of tree
set<int> st[maxn],st2 ;
set<pii> st0 ;
int id[maxn],sz,ioval[maxn] ; /// in + out number
bool ruse=0 ; /// whether root used as in point
inline void sub(int pos)
{
    st0.erase(pii(ioval[pos],pos)) ;
    ioval[pos]-- ;
    st0.insert(pii(ioval[pos],pos)) ;
}
 
int solve(int x)
{
    if(x!=cent) sub(id[x]) ;
    pii r=*st0.rbegin() ;
    if(r.F==n-x+1)
    {
        int pos=r.S , ret=*st[pos].begin() ;
        st2.erase(*st[pos].begin()) ;
        st[pos].erase(st[pos].begin()) ;
        if(!st[pos].empty()) st2.insert(*st[pos].begin()) ;
        sub(pos) ;
        return ret ;
    }
 
    int tmp=-1 ;
    if(x!=cent && !st[id[x]].empty()) st2.erase(tmp = *st[id[x]].begin()) ;
    int ret=st2.empty() ? cent : *st2.begin() ;
    if(!ruse && cent<ret) ruse=1 , ret=cent ;
    if(tmp!=-1) st2.insert(tmp) ;
 
    if(ret==cent) return ret ;
    int pos=id[ret] ;
    sub(pos) ;
    st2.erase(*st[pos].begin()) ;
    st[pos].erase(st[pos].begin()) ;
    if(!st[pos].empty()) st2.insert(*st[pos].begin()) ;
    return ret ;
}
 
main()
{
    scanf("%d",&n) ;
    for(int i=1;i<n;i++)
    {
        int x,y,dis ; scanf("%d%d%d",&x,&y,&dis) ;
        v[x].push_back((P){y,dis}) ;
        v[y].push_back((P){x,dis}) ;
    }
 
    int mi=n ;
    dfs(1,-1,cent,mi) ;
    d[cent]=0 ; dfs(cent) ;
 
    LL ans=0LL ;
    for(int i=1;i<=n;i++) ans+=d[i] ;
    printf("%I64d\n",2*ans) ;
    if(n==1){printf("1\n") ; return 0 ;}
    if(n==2){printf("2 1\n") ; return 0 ;}
 
    sz=v[cent].size() ;
    for(int i=0;i<sz;i++)
    {
        dfs2(v[cent][i].to,cent,st[i]) ;
        for(auto j : st[i]) id[j]=i ;
    }
    for(int i=0;i<sz;i++) st2.insert(*st[i].begin()) ;
    for(int i=0;i<sz;i++)
        st0.insert(pii(2*st[i].size(),i)) ,
        ioval[i]=2*st[i].size() ;
 
    for(int i=1;i<=n;i++) printf("%d%c",solve(i),i==n?'\n':' ') ;
}
 

沒有留言:

張貼留言