首页 > 其他分享 >点分治小记

点分治小记

时间:2024-04-24 13:24:10浏览次数:16  
标签:cnt int 分治 cin edges edge root 小记

点分治是一类高效统计树上路径问题的算法,通过优化递归深度的方法来有效保证时间复杂度。

具体操作一般是以下几步:

  • 找到当前子树的重心

  • 以重心为根计算经过根节点的路径对答案的贡献

  • 将根删去并递归处理它的所有子树

因为我们每次都以树的重心来作为根节点,递归深度不会超过 \(\log n\) 层。 每一层又顶多计算 \(n\) 个节点,因此时间复杂度为 \(O(n\log n)\)。

一般计算贡献有两种方式:

1.统计这棵树内所有路径贡献并将只在一棵子树内的减去。

2.直接枚举子树并计算贡献。

一般来说可以使用数据结构来辅助计算贡献,像线段树,树状数组以及单调队列都是不错的选择。

例题

P4178 Tree

第一种计算方式,配合一个双指针就可以了。

(主要是为了贴个代码)

#include<bits/stdc++.h>
#define int long long 
using namespace std;

const int N = 1e5 + 10;
struct edge{
	int v, w, next;
}edges[N << 1];
int head[N], idx;
int n, k, siz[N], dis[N], root, rtsiz, ans;
int tmp[N], tp;
bool vis[N];

void add_edge(int u, int v, int w){
	edges[++idx] = {v, w, head[u]};
	head[u] = idx;
}

void getwson(int u, int fa, int nodeCnt){ 
	siz[u] = 1; int mxsz = 0;
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v;
		if(v == fa || vis[v]) continue;
		getwson(v, u, nodeCnt); siz[u] += siz[v]; mxsz = max(mxsz, siz[v]);
	}
	mxsz = max(nodeCnt - siz[u], mxsz);
	if(mxsz < rtsiz) rtsiz = mxsz, root = u;
}

void getdis(int u, int fa, int d){
	tmp[++tp] = d;
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v;
		if((!vis[v]) && v != fa) getdis(v, u, d + edges[i].w);
	}
}
void calc(int flag){
	sort(tmp + 1, tmp + tp + 1); int j = 0, ret = 0;
	for(int i = tp; i > 0; i--){
		while(tmp[i] + tmp[j + 1] <= k && j < tp) j++;
		ret += (j - (j >= i)); 
	}
	ans += (ret / 2) * flag; 
}

void solve(int u, int fa, int nodeCnt){
	rtsiz = nodeCnt - 1, root = u;
	tp = 0; getwson(u, fa, nodeCnt);
	vis[root] = true; getdis(root, 0, 0); calc(1);
	for(int i = head[root]; i; i = edges[i].next){
		int v = edges[i].v;
		if(v == fa || vis[v]) continue;
		tp = 0; getdis(v, root, edges[i].w); calc(-1);
	}
	for(int i = head[root]; i; i = edges[i].next){
		int v = edges[i].v;
		if(v == fa || vis[v]) continue;
		solve(v, root, siz[v]);
	}
}


signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n;
	for(int i = 1; i < n; i++){
		int x, y, w;
		cin >> x >> y >> w;
		add_edge(x, y, w); add_edge(y, x, w);
	}
	cin >> k;
	solve(1, 0, n);
	cout << ans;
	
	return 0;
}

2.P4149 [IOI2011] Race

第二种计算方法。

开一个桶 \(cnt\) 记录,路径权值为 \(w\) 时,路径边数的最小值。

然后对于一棵子树 \(v\) 到根的路径 \(i\) 权值 \(w\),更新 \(ans = min(ans, dep[i] + cnt[k-w]])\)

注意计算的时候不要更新桶,一定要等到一棵子树的答案全部计算完之后再更新。

code:

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e6 + 10, INF = 0x3f3f3f3f;
struct edge{
	int v, w, next;
}edges[N << 1];
int head[N], idx;
int n, k, dep[N], siz[N], dis[N], root, rtsiz, ans = INF;
int tmp[N], tp, cnt[N];
bool vis[N];

void add_edge(int u, int v, int w){
	edges[++idx] = {v, w, head[u]};
	head[u] = idx;
}

void getroot(int u, int fa, int ncnt){
	siz[u] = 1; int mxsz = 0;
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v;
		if(v == fa || vis[v]) continue;
		getroot(v, u, ncnt); siz[u] += siz[v]; mxsz = max(mxsz, siz[v]);
	}
	mxsz = max(mxsz, ncnt - siz[u]);
	if(mxsz < rtsiz) rtsiz = mxsz, root = u;
}

void getdis(int u, int fa, int d, int d2){
	tmp[++tp] = d; dep[tp] = d2; siz[u] = 1; 
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v;
		if(v != fa && (!vis[v])) getdis(v, u, d + edges[i].w, d2 + 1), siz[u] += siz[v];
	}
}

void solve(int u, int fa, int ncnt){
	rtsiz = ncnt - 1, root = u;
	tp = 0; getroot(u, fa, ncnt);
	vis[root] = true; dep[root] = 0;
	for(int i = head[root]; i; i = edges[i].next){
		int v = edges[i].v;
		if(vis[v] || v == fa) continue; 
		getdis(v, root, edges[i].w, 1);
		for(int j = tp - siz[v] + 1; j <= tp; j++) if(tmp[j] <= k) ans = min(ans, dep[j] + cnt[k - tmp[j]]);
		for(int j = tp - siz[v] + 1; j <= tp; j++) if(tmp[j] <= k) cnt[tmp[j]] = min(cnt[tmp[j]], dep[j]);
	} 
	for(int i = 1; i <= tp; i++) if(tmp[i] <= k) cnt[tmp[i]] = INF;
	for(int i = head[root]; i; i = edges[i].next){
		int v = edges[i].v;
		if((!vis[v]) && u != fa) solve(v, root, siz[v]);
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0); 
	cin >> n >> k; memset(cnt, 0x3f, sizeof cnt); cnt[0] = 0;
	for(int i = 1; i < n; i++){
		int u, v, w; cin >> u >> v >> w;
		add_edge(v + 1, u + 1, w); add_edge(u + 1, v + 1, w);
	}
	solve(1, 0, n); 
	if(ans != INF) cout << ans;
	else cout << -1;
	
	return 0;
}

726f6a57-7dd3-4e25-8f40-391d8570e879

标签:cnt,int,分治,cin,edges,edge,root,小记
From: https://www.cnblogs.com/little-corn/p/18155066

相关文章

  • 网络流小记
    基本定义:网络:一张有向图。流量:经过一条边的流的大小,一条边\((u,v)\)的流量记为\(flow(u,v)\),一个网络的流量定义为\(∑f(s,x)\)。容量:一条边的流量上限,一条边\((u,v)\)的容量记为\(cap(u,v)\)。费用:经过一条边单位流量的所需费用,一条边\((u,v)\)的费用记为......
  • P7431【THUPC2017】小 L 的计算题 (牛顿恒等式)(分治NTT)(多项式求逆)题解
    知识点涉及:牛顿恒等式,分治\(NTT\),多项式求逆。这道题有一个推式子之后分治\(NTT+Ln+Exp\)的做法,不过也有一个不用\(Ln+Exp\)的做法(理论常数要小点,实际差不多)。题解:这道题可以牛顿恒等式直接推出一个非常好写的东西。首先看一下牛顿恒等式的描述:对于\(n\)次多项式\(A(......
  • 小记 Demo
    定义领域模型:AggregateRoot:定义SalesOrder作为聚合根,其中包括订单明细、客户信息、订单总额等。Entity:定义如OrderItem(订单项)、Inventory(库存)等实体。ValueObject:定义如Address(地址)、Money(金额)等值对象。建立仓储接口:使用ABPvNext框架的仓储模式来实现数据的......
  • 点分治
    1概念点分治是树分治的一种,主要用于处理树上路径问题。注意这颗树不需要有根,即这是一颗无根树。下面以例题分析点分治的基本思想。2基本思想首先你需要会的前置知识:树的重心。我们来看这样一道例题:【模板】点分治:给出一颗无根树,有边权,询问树上是否存在距离为\(k\)的......
  • 博弈论小记
    以下我们都考虑这样一种游戏:两个人,轮流进行;游戏总是在有限步内结束;同一个状态不可能多次抵达,且没有平局;每个时刻的合法决策集合仅与当前局面有关,而与游戏者无关;不能操作者输。我们定义:必败态:无论如何先手必败的状态(局面)。必胜态:先手存在必胜策略的状态(局面)。......
  • hexo 折腾小记
    hexo是一套静态网页生成框架类似的还有JekyllGitHub默认推荐的框架/Hugo(......
  • 点分树(动态点分治)学习笔记
    1.定义在点分治的基础上加以变化,构造一颗支持快速修改的重构树,称之为点分树2.算法2.1.思路点分治的核心在于通过树的重心来划分联通块,减少合并层数,从而降低时间复杂度所以,我们可以按分治递归的顺序提出一颗树,易知树高至多为logn具体的说,对于每一个找到的重心,将上一次分治......
  • windows平台vs2019编译Luabind小记
    前言写这篇文章的目的是Luabind这个库比较老旧,对于新编译器需要做一些代码上的兼容,参考资料又都有点过时,所以特写此篇,记录踩坑过程;参考资料用VS2010编译luabind如何编译luabind支持vs2010之后所有版本Lua官网LuabindRepo编译前准备准备相关前置组件基本编译依赖Des......
  • 1.0 多层感知机&BP传播 小记
    1.感知机与线性模型单层感知机的表达式和线性分类表达式等同,可以将一个单层感知机看作是一个线性分类器。单层感知机可以解决与、或、非的分类问题,但是不能解决异或分类(非线性)问题。howtosolvetheproblem:多个线性分类器解决线性不可分问题,即:多个单层感知机组合叠加解......
  • 13年 史密斯热水器全拆清洗 小记
    ~~~~~~~~~~~~~~~排水~~~~~~~~~~~~~~~拆机过程不复杂,提前断开电源,切断进水阀门.用过导水管从进水口排干净热水器中的水即可,注意排水过程需要打开热水器出水口,用来进气.排干净水之后就可以拆下来进出水管,这样热水器就从水路上分离开来了.把热水器从挂墙上拿下来,50L的......