点分治学习笔记

点分治

学习链接

通过这个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);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章