Processing math: 0%

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_1P的大小> \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不是重心,他落在PT_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':' ') ;
}
 

沒有留言:

張貼留言