zoukankan      html  css  js  c++  java
  • 点分治总结

    点分治常用于静态树上的路经统计问题,我们可以很自然的设计出这样一种分治算法:
    1.找出根结点Root;
    2.计算以Root为根的树的答案;
    3.删除结点Root,分治解决Root的每个子树;
    但这样并不是最优,当树退化成链时,递归层数就会退化为O(N), 整个程序时间复杂度也会退化成O(N ^ 2)。
    为了解决这个问题,我们每次找的Root必须是树的重心。这样的话递归的复杂度就能稳定在O(logN)。

    树的重心:

    树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。 --百度百科
    

    不难发现,每次取树的重心作为Root,其所有子树大小都不大于(size[Root]/2),((size[Root])表示以Root为根的子树的结点数),这样递归下去,复杂度就可以控制在O(lonN)。
    如何找重心: 一遍dfs就可以。

    void find(int x, int fa) {
    	size[x] = 1; maxp[x] = 0;
    	//size[x] 以x为根的子树的结点数 maxp[x] 与x相连的所有子树中最大子树的大小
    	for(int i = head[x]; i; i = nextt[i]) {
    		int y = to[i]; if(y == fa || vis[y]) continue;
    		find(y, x);
    		maxp[x] = max(maxp[x], size[y]);
    		size[x] += size[y];
    	}
    	maxp[x] = max(maxp[x], sum - size[x]);
    	if(maxp[x] < maxp[root]) root = x;
    }
    

    如何点分治:

    void solve(int x) {
    	ans[0] = vis[x] = true; calc(x); //calc() 统计答案
    	for(int i = head[x]; i; i = nextt[i]) {
    		int y = to[i];
    		if(vis[y]) continue;
    		sum = size[y];
    		root = 0; maxp[root] = inf;
    		find(y, y);//对于每个子树找重心,递归
    		solve(root);
    	}
    }
    

    如何统计答案:
    以luoguP3806 【模板】点分治1 为例
    将所有询问记在一个数组中,一遍点分治直接统计完。
     用一个桶记录一下当前子树到根结点距离为某个数的点是否存在,为避免两个点在同一子树中导致路径重复,我们每次计算完一个子树,再将桶更新。
    在其他许多路径计数问题中,这样类似的思想可以避免重复计算,也就免去了容斥这一步,降低了思维难度。

    void calc(int x) {
    	int p = 0, save[N];//用于清空桶
    	for(int i = head[x]; i; i = nextt[i]) {
    		int y = to[i]; if(vis[y]) continue;
    		dis[y] = d[i]; tot = 0; dfs(y, x); //遍历子树,计算dis[]
    		for(int j = tot; j >= 1; j--) {
    			for(int q = 1; q <= m; q++) {
    				if(qu[q] >= tmp[j]) flag[q] |= ans[qu[q] - tmp[j]];//判断是否存在
    			}
    		}
    		for(int j = 1; j <= tot; j++) {//更新桶
    			save[++p] = tmp[j];
    			ans[tmp[j]] = true;
    		}
    	}
    	for(int i = 1; i <= p; i++) ans[save[i]] = false;//memset()会Tle
    }
    

    完整代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    const int N = 10005;
    const int inf = (1 << 31) - 1;
    int n, m, cnt = 1, k, root, size[N], maxp[N], qu[N];
    int nextt[N << 1], head[N], to[N << 1], d[N << 1];
    int sum, tot = 0, tmp[N], dis[N];
    bool vis[N], ans[100000005], flag[N];
    void add(int x, int y, int z) {
    	nextt[++cnt] = head[x]; d[cnt] = z;
    	to[cnt] = y; head[x] = cnt;
    }
    void find(int x, int fa) {
    	size[x] = 1; maxp[x] = 0;
    	//size[x] 以x为根的子树的结点数 maxp[x] 与x相连的所有子树中最大子树的大小
    	for(int i = head[x]; i; i = nextt[i]) {
    		int y = to[i]; if(y == fa || vis[y]) continue;
    		find(y, x);
    		maxp[x] = max(maxp[x], size[y]);
    		size[x] += size[y];
    	}
    	maxp[x] = max(maxp[x], sum - size[x]);
    	if(maxp[x] < maxp[root]) root = x;
    }
    void dfs(int x, int fa) {
    	tmp[++tot] = dis[x];
    	for(int i = head[x]; i; i = nextt[i]) {
    		int y = to[i]; if(vis[y] || y == fa) continue;
    		dis[y] = dis[x] + d[i];
    		dfs(y, x);
    	}
    }
    void calc(int x) {
    	int p = 0, save[N];//用于清空桶
    	for(int i = head[x]; i; i = nextt[i]) {
    		int y = to[i]; if(vis[y]) continue;
    		dis[y] = d[i]; tot = 0; dfs(y, x); //遍历子树,计算dis[]
    		for(int j = tot; j >= 1; j--) {
    			for(int q = 1; q <= m; q++) {
    				if(qu[q] >= tmp[j]) flag[q] |= ans[qu[q] - tmp[j]];//判断是否存在
    			}
    		}
    		for(int j = 1; j <= tot; j++) {//更新桶
    			save[++p] = tmp[j];
    			ans[tmp[j]] = true;
    		}
    	}
    	for(int i = 1; i <= p; i++) ans[save[i]] = false;//memset()会Tle
    }
    void solve(int x) {
    	ans[0] = vis[x] = true; calc(x); //calc() 统计答案
    	for(int i = head[x]; i; i = nextt[i]) {
    		int y = to[i];
    		if(vis[y]) continue;
    		sum = size[y];
    		root = 0; maxp[root] = inf;
    		find(y, y);
    		solve(root);
    	}
    }
    int main() {
    //	freopen("data.in", "r", stdin);
    	scanf("%d%d", &n, &m);
    	for(int i = 1, u, v, w; i <= n - 1; i++) {
    		scanf("%d%d%d", &u, &v, &w);
    		add(u, v, w); add(v, u, w);
    	}
    	for(int i = 1; i <= m; i++) {
    		scanf("%d", &qu[i]);
    	}
    	root = 0; sum = n; maxp[root] = n;
    	find(1, 1);
    	solve(root); 
    	for(int i = 1; i <= m; i++) 
    		flag[i] == true ? printf("AYE
    ") : printf("NAY
    ");
    	return 0;
    }
    

    其他例题:P4178 Tree , P2634 [国家集训队]聪聪可可

  • 相关阅读:
    转载--C 的回归
    学嵌入式不是你想的那么简单--转载
    scanf() 与 gets()--转载
    getchar、getch、getche 与 gets()
    scanf()函数原理
    C/C++头文件一览
    再论函数指针、函数指针数组
    初论函数指针、指针函数、指针的指针
    转载--一个“码农”自述的血泪史:当了35年程序员,我最大的遗憾就是没抓住机遇转行
    转载--协方差的意义和计算公式
  • 原文地址:https://www.cnblogs.com/mcggvc/p/12304733.html
Copyright © 2011-2022 走看看