\(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 ...