淺析點分治實現過程及時間複雜度

概要

帶權無根樹上簡單路徑統計問題的算法
將樹上問題轉化爲子問題求解,每次統計字節點貢獻求和便可node

引入

Luogu P4178 Tree
題目大意,給一棵樹一個\(k\),求距離小於等於\(k\)的點對數量ios

暴力

\(LCA\)板子直接\(T30\)沒啥說的算法

正解——點分治

點分治

  • 從一個點開始\(dfs\),求出到這個點距離
  • 枚舉距離\(l\)\(r\),對於\(a[l]+a[r]<=k\)統計答案

根的選取(重心的定義)

對於一棵無根樹,找到一個點,使得知足若是以它爲根,它的最大子樹大小盡可能小,這個點稱爲重心。

好比這條鏈狀結構,若是選取1爲根節點,遞歸時間複雜度飆升至\(O(N^2)\),若是選取重心3做爲根節點那麼時間複雜度維持在\(O(nlog_n)\)數組

重心的性質

  • 刪去該點後,最大子樹的大小最小
  • 以樹的重心爲根的每棵子樹大小不超過\(\frac n 2\)(證實……yy一下就行了吧,太簡單了不寫了)
  • 由第二條推導出,遞歸整棵樹的時間複雜度是\(O(log\;n)\)
  • 一個子節點統計一次答案複雜度爲\(O(n\;log\;n)\)

算法流程

  • \(dfs\)查找樹的重心
  • \(dfs\)求出每一個點到重心的距離,而且將距離存進一個數組
  • 枚舉距離數組中知足\(a[i]+a[j]<=k\)的狀況統計答案
  • 遞歸至下一個子節點重複上述步驟直至整棵樹搜索完畢

特殊處理

在處理樹上兩個點的時候,兩點的位置關係一共有三種ide

  • 兩點在同一棵子樹上
  • 兩點在不一樣子樹上
  • 一個點在子樹內,一個是重心
    顯然,對於2和3兩種狀況沒啥問題
    可是對於第一種狀況

    顯然,2和4的距離在實現過程當中會有兩種處理方式,一種是經過簡單路徑,一種是經過1計算的路徑
    第二種是不合法的,因此統計答案時要減去

Code

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 1000010;

struct node{
	int to, nxt, w;
}edge[ss << 1];

int head[ss << 1], tot;
inline void add(int u, int v, int w){
	edge[++tot].to = v;
	edge[tot].nxt = head[u];
	edge[tot].w = w;
	head[u] = tot;
}

int size[ss], sz, maxx[ss], root;
bool vis[ss];
inline void getroot(register int u,register  int f){
	size[u] = 1;
	maxx[u] = 0;
	for(register int i = head[u]; i; i = edge[i].nxt){
		register int v = edge[i].to;
		if(v == f || vis[v]) continue;
		getroot(v, u);
		size[u] += size[v];
		maxx[u] = max(maxx[u], size[v]);
	}
	maxx[u] = max(maxx[u], sz - size[u]);
	maxx[u] = maxx[u];
	if(maxx[u] < maxx[root]) root = u;
}

int a[ss], cnt;
inline void getdis(int u, int f, int d){
	a[++cnt] = d;
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(v == f || vis[v]) continue;
		getdis(v, u, d + edge[i].w);
	}
}

int n, k;
inline int calc(int u, int d){
	int sum = 0;
	cnt = 0;
	getdis(u, 0, d);
	sort(a + 1, a + 1 + cnt);
	int r = cnt;
	for(int l = 1; l <= cnt; l++){
		while(r && a[l] + a[r] > k) r--;
		if(l > r) break;
		sum += r - l + 1;
	}
	return sum;
}

int ans;
inline void divide(int u){
	ans += calc(u, 0);
	vis[u] = 1;
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(vis[v]) continue;
		ans -= calc(v, edge[i].w);
		root = 0;
		sz = size[v];
		getroot(v, u);
		divide(v);
	}
}

signed main(){
	n = read();
	for(int i = 1; i <= n - 1; i++){
		int u = read(), v = read(), w = read();
		add(u, v, w);
		add(v, u, w);
	}
	k = read();
	maxx[0] = 0x7fffffff;
	getroot(1, 0);
	divide(root);
	cout << ans - n << endl;
	return 0;
}

小結

掌握分治思想&容斥操做spa

相關文章
相關標籤/搜索