這兩天跟著學了一手樹上點分治模板,然後有一些感悟,決定來寫一發部落格.
首先,鑑於鄙人的經驗,如果想要較快速地學習乙個新演算法,肯定還是先看一道經典的例題比較好,所以我們先來一道例題.
給你一棵tree,以及這棵樹上邊的距離.問有多少對點它們兩者間的距離小於等於k
輸入格式:
n(n<=40000) 接下來n-1行邊描述管道,按照題目中寫的輸入 接下來是k
輸出格式:
一行,有多少對點之間的距離小於等於k
所謂點分治,顧名思義,就是將一棵樹上的點按照一定的順序分開來處理.然後我們這裡找的是一棵樹的重心.然後以重心為根.並且遞迴處理每一棵子樹.
所謂樹的重心,其實就是找乙個結點為根,然後使得它的子樹裡面子節點數最多的子樹子節點數最小.
a其實想一想傳統的樸素演算法時間超限的主要原因就是因為每次去找到乙個點的距離,都因為樹的深度可能會比較深 ( 尤其可能會退化成一條鏈 ) ,而導致每一次遞迴的次數很多.所以就會效率很低.
然而其實和 splay 相似的目的,我們使用重心也是為了使得每一次操作的深度都盡可能低.然而重心又滿足上文中的性質.
然後我們來看一手如何找到重心.
int getroot(int u,intfa) }
maxson[u]=max(maxson[u],s-size[u]);
if(maxson[u]root=u,mmx=maxson[u];
}
在上面這個**中,size 代表的是每乙個結點的子樹的結點個數.
然後 maxson 代表的是當前這個結點的子樹裡面最多的子節點個數, vis 代表的是當前這個點是否已經被刪除過..
maxson[u]=max(maxson[u],s-size[u]);
然後這一行,因該也比較好理解.因為除了它當前的子節點外,它的父親那邊也還連著一棵樹.
這樣的話其實是 時間複雜度是 o (n);
找完 root 之後就是分治了. 那麼具體怎麼分治呢,我們也來看一手**.
void divide(intrt) }
return
;}
在這其中,s代表的是當前尚還剩餘的結點個數.
然後在這裡,我們做的操作是遞迴處理每一棵子樹.然後外加統計答案.
統計答案這裡有兩次:
ans=ans+findans(rt,0);
這一次是將當前這棵子樹裡面所有的距離小於等於k 的點對全部找出來.
然後另外一次:
ans-=findans(tt,a[i].w);
這裡是將一開始多統計的減去.
這個東西應該也比較好理解.(這個和後面統計答案的方式有關).
因為我們統計答案的時候總是先處理一遍所有的點到當前根節點的距離,然後通過佇列一次次求和然後比較.然後統計得出答案.
這樣我們很明顯只考慮了點與點之間的距離關係( 也是為了更加簡便 否則我們需要去考慮其他一些問題會極大的將問題複雜化 ).
會有一些點是不合法的,比如說兩個點在一條路徑上或者統計的兩個點到根構成的路徑根本就不是一條簡單路徑 (一條鏈) .
但是因為我們只考慮了距離問題,所以只要兩個點到根節點的距離之和小於k,我們也將其計入.
於是乎此時我們會發現,其實我們一開始已經將所有的路徑(包括不合法) 都已經計入,然後在之後的子樹處理中,我們就需要將多出來的刪去.
以上,似乎就是我對分治的理解,相信我,我會填 動態點分治 和 分治cdq 這兩個坑的 還有後面的習題也會填上的.
//luogu-judger-enable-o2
#includeusing
namespace
std;
const
int maxn=40008
;const
int inf=1e10+9
;int
k;struct
sja[maxn*2
];int
sizeb,head[maxn];
intans,s;
//s 是當前這棵樹的結點數量
intdep[maxn],vis[maxn];
//vis 代表當前這個點是否已經被刪除
intfa[maxn],n,size[maxn];
void add(int x,int y,int
z)int
root;
intmaxson[maxn],mmx;
int getroot(int u,int
fa) }
maxson[u]=max(maxson[u],s-size[u]);
//除了它下面的子節點外 還有一棵經過經過父親結點的樹
if(maxson[u]root=u,mmx=maxson[u];
}int
dis[maxn],summar;
void getdis(int u,int fa,int
dist)
//通過遞迴求出每個點到當前這個根的距離
}int findans(int sta,int
len)
void divide(int
rt) }
return;}
intmain()
scanf("%d
",&k);
mmx=inf; s=n;
getroot(
1,0);
divide(root);
cout
return0;
}
點分治模板
luogu 3806 近些日子學了點分治,當然只是學了個模板。所謂點分治,使用於處理樹上路徑的一種分治手段。因為利用了重心的性質,時間複雜度可以保證呢。所謂演算法流程 選取當前子樹的重心 計算路徑總數,不管路徑是否過當前重心 後面會去重 計算起點和終點在同一顆子樹中的合法路徑 因為這條路徑不是簡單路...
點分治模板
bzoj1316 由於之前板子寫得太爛了,今天把它重新整理改進了一下 vis表示每個點是否已經當過根,所以注意dfs,findroot函式的計算過程中是不會對vis進行修改的 修改時只需要考慮對dfs和solve中的有關位置進行修改即可,其它部分基本不變 include using namespac...
模板 點分治
poj 1741 include include using namespace std const int n 10010 const int inf 0x3f3f3f3f int n,k,ans int head n nxt 2 n to 2 n w 2 n tot 鏈式前向星 void add...