點分治學習筆記

2022-05-11 02:53:36 字數 4130 閱讀 5174

\(point~divide~and~rule\)

澱粉質就是在樹上,依靠不停的遞迴和分治,解決相同的子問題

先來看看模板題: \(tree\)

就是找樹上\(<=k\)的路徑有多少

我們可以分兩種情況討論

\(1.\) 經過根節點\(p\)的路徑

\(2.\) 不經過根節點\(p\)的路徑

第二種情況可以通過遞迴來處理,我們直接來討論第一種情況

設當前的樹根節點為 \(p\)

顯然一條經過\(p\)路徑可以由兩條到端點為\(p\)的鏈組成

顯然,會出現重複的情況,例如:

(好大...

這兩條鏈組成的已經不是一條路徑了,我們需要去除這種非法情況,這裡提供兩種方法

我們只需要統計\(p\)樹內子節點到\(p\)的距離,排序後,用雙指標做,再把\(p\)子節點的非法情況用種方法去除

我們只需要把\(p\)樹的子樹依次處理,對於\(p\)的一棵子樹\(y\),先把所有在\(y\)樹里的路徑長度\(dis\)統計出來,再在\(treap\)裡找\(<=k-dis\)的個數,最後將這些路徑放入\(treap\)

(推薦使用這一種,一般來說大多數題目都需要不用\(treap\),暴力開桶就行了,這種技巧十分重要

這兩種方法都要會掌握

統計完成了,那我們怎麼遞迴呢?

這裡有乙個核心,我們每次選擇只選重心來遞迴,由於中心的子節點\(size\)永遠不會超過\(root~size/2\),所以我們的遞迴層數不會超過\(log(n)\)

每一次計算是\(nlogn\),遞迴層數\(log(n)\),總時間複雜度是\(o(nlognlogn)\),巧妙的暴力

\(code:\)(這裡用的是第一種去重方法,太懶了)

#includeusing namespace std;

const long long n=4e4+10;

struct data a[n*2];

long long head[n],max_size[n],size[n],vis[n],root,k,dis[n],cnt,n,tot,ans,size_sontree;

void insert(long long x,long long y,long long z)

long long get_root(long long x,long long fa)

max_size[x]=max(max_size[x],size_sontree-size[x]);

if(max_size[x]k&&r) r--;

if(l>r) break;

sum+=r-l;

l++;

} return sum;

}void slove(long long x) }}

int main()

scanf("%lld",&k);

max_size[0]=int_max;

size_sontree=n;

get_root(1,0);

slove(1);

printf("%lld",ans);

}

找出最小的\(dis>=s\)即可,由於是取最小值,所以容斥就不行了,這裡使用的是\(set\)

#include using namespace std;

const int n = 1e5 + 10;

int max_size[n], size_sontree, size[n], head[n], dis[n], vis[n], root, tot, cnt, n, s, e, ans;

struct data a[n * 2];

void insert( int x, int y, int z )

int get_root( int x, int fa )

max_size[x] = max( max_size[x], size_sontree - size[x] );

/* printf("mxsize[%d]=%d\n",x,max_size[x]); */

if ( max_size[x] < max_size[root] )

root = x;

}void find_dis( int x, int fa, int d )

}int calc( int x )

for ( int j = 1; j <= cnt; j++ )

q.insert( dis[j] ); }}

void slove( int x )

}int main()

max_size[0] = int_max;

size_sontree = n;

get_root( 1, 0 );

slove( root );

if ( ans <= e )

printf( "%d", ans );

else printf( "-1" );

}

這裡有兩個條件:

\(1.\)路徑上黑色邊與白色邊的數量相同。

\(2.\)路徑中存在乙個不同於起始點和終點的點,它到終點的路徑也滿足\(1\)。

把黑看成\(1\),白看成\(-1\),兩條鏈的\(dis\)為相反數即可滿足\(1\)條件

討論第二種條件:

如果一條鏈\(p->u\)的\(dis\)在這條鏈中間任意一點\(p->v\)的\(dis\)也出現過,那麼\(u->v\)的\(dis\)就為\(0\),我們稱這條鏈為\(can\)鏈,

處理\(can\)鏈可以用遞迴來做,開個\(map\)統計\(dis\)的出現

我們發現,只要兩條鏈只要有一條\(can\)鏈,就能滿足條件\(2\),開兩個\(2維map\)來統計\(can鏈\)和非\(can鏈\)的個數,用類似第二種情況去重,還需要特判單獨一條鏈的情況

\(code:\)(此題細節較多)

#include using namespace std;

const long long n = 1e6 + 10;

long long max_size[n], size_sontree, size[n], head[n], dis[n], vis[n], root, tot, cnt, n, s, e, ans, use[n];

mapduck;

mapmp1;

mapmp0;

struct data a[n * 2];

void insert( long long x, long long y, long long z )

long long get_root( long long x, long long fa )

max_size[x] = max( max_size[x], size_sontree - size[x] );

if ( max_size[x] < max_size[root] )

root = x;

}void find_dis( long long x, long long fa, long long d )

duck[d]++;

for ( long long i = head[x]; i; i = a[i].stb )

duck[d]--;

}long long calc( long long x )

for ( long long l = last + 1; l <= cnt; l++ )

last = cnt;

} for ( long long l = 1; l <= cnt; l++ ) }

void slove( long long x )

}int main()

max_size[0] = int_max;

size_sontree = n;

get_root( 1, 0 );

slove( root );

printf( "%lld", ans );

}

其他的直接套模板,主要是去重

\(fhq-treap\)並沒卵用

完結撒

點分治學習筆記

點分治主要用來處理樹上路徑問題,可以統計樹上點到點的所有路徑,複雜度o nlogn 基於樹上的結點進行分治,不斷將一棵樹拆成多顆子樹處理 選擇點時為了防止退化成鏈的情況,如果選點後左右子樹越大,遞迴層數越多,時間越慢,反之則越快,我們每次選擇子樹內的重心 void getroot int u,int...

點分治學習筆記

關於點分治,其實思想是非常好理解的,模擬在數列上或是在平面上的分治演算法 如歸併排序,平面最近點對等 我們可以從字面上理解該演算法 以乙個點為界限,將一棵樹分成若干個子樹,當劃分到一定規模,就對每個子樹分別進行求解 感性理解就好了 感受乙個演算法最直觀的辦法,就是來看一道模板題。給定一棵有 n 個點...

點分治學習筆記

銀月城傳送門 集訓day9上午各種神仙分治真心聽不懂,自己頹著把點分治學了,簡單 並不 寫一下再加深一下理解吧。一 點分治是個啥 點分治是通常用來處理樹上路徑統計問題的一種分治方法。尤其適用於大規模的資料處理。時間複雜度最高為o nlogn 例如求樹上兩點間距離,點分治就遠優於dfs求兩點距離 o ...