點分治學習筆記

點分治

學習鏈接

通過這個blog入門的:https://www.luogu.org/blog/user9012/dian-fen-zhi-lve-xie

簡介

在做一類樹上的題目時,往往需要對樹進行分治,能將子樹分解成大小盡量相等的情況是最吼的,因此我們每次選擇根節點的時候都應該是當前部分的重心.

因此點分治模板包含了兩個主要的過程:尋找當前部分的根和點分治主遞歸.

算法模板

尋找重心

1.siz[u]siz[u],表示子樹大小.
2.use[u]=1use[u] = 1的點不能訪問,這限制了重心只能在當前部分尋找.
3.big[u]big[u],表示以uu爲根節點最大子樹大小.
4.rtrt,表示當前部分的重心.
5.allall,表示當前部分的大小.

int siz[N],use[N],big[N],rt,all;
void getroot(int u,int fa) {
	siz[u] = 1;big[u] = 0;
	for(auto p : G[u]) {
		int v = p.first;
		if(use[v] || v == fa) continue;
		getroot(v,u);
		siz[u] += siz[v];
		big[u] = std::max(big[u],siz[v]);
	}
	big[u] = std::max(big[u],all-siz[u]);
	if(big[u] < big[rt]) rt = u;
}

代碼實現比較簡單,很容易理解.

點分治主模板

void dfs(int u) {
	//calc過程是求解與u有關的問題.
	use[u] = 1;calc(u);
	for(auto p : G[u]) {
        int v = p.first;
		if(use[v]) continue;
		rt = 0;all = siz[v];
		//尋找子問題的重心,並遞歸處理
		getroot(v,u);dfs(rt);
	}
}

例題

P3806 點分治模板題

uu的每顆子樹記錄一個setset,裏面存這個子樹中的點到uu節點的距離可能值.

uu節點存一個finfin,表示遍歷過的子樹中的點到uu點的可行距離.

每回溯完一顆子樹後,先判斷該子樹能否與之前的子樹形成距離爲kk的點對,然後再把該子樹的setset合併到finfin中去.

代碼只貼calccalc部分

void calc(int u) {
    std::unordered_set<int> fin;
    fin.insert(0);
    for(auto p : edge[u]) {
        int v = p.first;
        int c = p.second;
        if(use[v]) continue;
        std::unordered_set<int> child;
        getdis(child,v,u,c);
        for(auto x : child) {
            rep(i,1,m) {
                if(fin.count(ask[i] - x)) {
                    ans[i] = 1;
                }
            }
        }
        for(auto x : child)
            fin.insert(x);
    }
}

CF161D 求距離爲k的點對數

同上一題,將setset改爲mapmap,其中map[i]map[i]表示距離爲ii的點有多少個即可.

void calc(int u) {
	map fin;
	fin[0] = 1;
	for(auto v : G[u]) {
		if(use[v]) continue;
		map child;
		getdis(child,v,u,1);
		for(auto p : child) {
			ans += fin[k-p.first] * p.second;
		}
		for(auto p : child) {
			fin[p.first] += p.second;
		}
	}
}

P4178 求距離小於k的點的對數

還是使用mapmap來維護,只不過在計算答案的時候,需要用雙指針來掃描.

void calc(int u) {
    map fin;
    fin[0] = 1;
    for(auto pp : G[u]) {
        int v = pp.first,c = pp.second;
        if(use[v]) continue;
        map child;
        getdis(child,v,u,c);
        int sum = 0;
        auto itf = fin.begin();
        for(auto itc = child.rbegin();itc != child.rend();++itc) {
            while(itf != fin.end() && itf->first + itc->first <= k) {
                sum += itf->second;
                ++itf;
            }
            ans += sum * itc->second;
        }
        for(auto p : child) {
            fin[p.first] += p.second;
        }
    }
}

P4149 求權值等於k的路徑最小邊數

這題也很簡單,僅僅需要修改一下mapmap表示的含義即可.

map[i]map[i]表示距離爲ii的路徑最少邊數.

void calc(int u) {
    map fin;
    for(auto pp : G[u]) {
        int v = pp.first,c = pp.second;
        if(use[v]) continue;
        map child;
        getdis(child,v,u,c,1);
        for(auto p : child) {
            if(p.first == k)
                ans = std::min(ans,p.second);
            else if(fin[k-p.first])
                ans = std::min(ans,fin[k-p.first]+p.second);
        }
        for(auto p : child) {
            if(fin[p.first] == 0)
                fin[p.first] = p.second;
            else
                fin[p.first] = std::min(fin[p.first],p.second);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章