点分治-summy

点分治-summmy

点分治就是在树上以重心分治,每次摘除重心,把树分成若干的siz较小的快,之后再递归处理。

主要有两种写法,

一种是在根处统计所有到根的信息,然后两两合并,再减去强制经过某个节点的贡献,

另一种是强制进入某个子树,得到这个子树的信息,再用这份信息与之前的合并。

应用1 , 有关树上的路径

给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000

这个适合用第二种,维护d[x] 到x的权值和, t[x] 到x的边数。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long LL;
const int N = 3e5+10;
inline int read()
{
	register int x = 0 , f = 0; register char c = getchar();
	while(c < '0' || c > '9') f |= c == '-' , c = getchar();
	while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0' , c = getchar();
	return f ? -x : x;
}
int n , K , cnt , rot , all , Ans;
int head[N] , mx[N] , siz[N] , vis[N] , val[1000100] , t[N];
LL d[N];
struct edge{ int v , nex , c; }e[N << 1];
inline void addedge(int u , int v , int c) { e[++cnt].v = v; e[cnt].nex = head[u]; e[cnt].c = c; head[u] = cnt; return ; }

void getroot(int x , int fa)
{
	siz[x] = 1; mx[x] = 0;
	for(int i = head[x] , v; i ; i = e[i].nex)
	{
		v = e[i].v; if(v == fa || vis[v]) continue;
		getroot(v , x); siz[x] += siz[v];
		mx[x] = max(mx[x] , siz[v]);
	}
	mx[x] = max(mx[x] , all - siz[x]);
	rot = mx[rot] > mx[x] ? x : rot;
}

void Insert(int x , int fa)
{
	if(d[x] <= K) val[d[x]] = min(val[d[x]] , t[x]);
	for(int i = head[x] ; i ; i = e[i].nex) if(e[i].v != fa && !vis[e[i].v]) Insert(e[i].v , x);
}

void Delete(int x , int fa)
{
	if(d[x] <= K) val[d[x]] = n;
	for(int i = head[x] ; i ; i = e[i].nex) if(e[i].v != fa && !vis[e[i].v]) Delete(e[i].v , x);
}

void calc(int x , int fa)
{
	if(d[x] <= K) Ans = min(Ans , val[K - d[x]] + t[x]);
	for(int i = head[x] ; i ; i = e[i].nex) 
		if(e[i].v != fa && !vis[e[i].v]) d[e[i].v] = d[x] + e[i].c , t[e[i].v] = t[x] + 1 , calc(e[i].v , x);
}

void solve(int x)
{
	vis[x] = 1; val[0] = 0;
	for(int i = head[x] ; i ; i = e[i].nex) if(!vis[e[i].v]) d[e[i].v] = e[i].c , t[e[i].v] = 1 , calc(e[i].v , 0) , Insert(e[i].v , 0);
	for(int i = head[x] ; i ; i = e[i].nex) if(!vis[e[i].v]) Delete(e[i].v , 0);
	for(int i = head[x] ; i ; i = e[i].nex) if(!vis[e[i].v]) all = siz[e[i].v] , rot = 0 , getroot(e[i].v , x) , solve(rot);
	return ;
}

int main()
{
	Ans = n = read(); K = read();
	int u , v , c;
	for(int i = 1 ; i < n ; ++i) u = read() + 1 , v = read() + 1 , c = read() , addedge(u , v , c) , addedge(v , u , c);
	for(int i = 1 ; i <= K ; ++i) val[i] = n;
	all = mx[0] = n; getroot(1 , 0); solve(rot);
	cout << (Ans == n ? -1 : Ans) << '\n';
	return 0;
}
/*
4 3
0 1 1
1 2 2
1 3 4
*/

给出一棵 n 个点的树,每条边的边权为1或0。求有多少点对 (i,j) ,使得:i 到 j 的简单路径上存在点 k (异于 i 和 j ),使得 i 到 k 的简单路径上0和1数目相等,j到 k 的简单路径上0和1数目也相等。

还是统计路径,维护出 \(f[x][0 / 1] , g[x][0 / 1]\) 即可,f 是 1 比 0 多 x 个有没有找到中间点 k , g 是0 比 1 多x个有没有找到中间点k的方案数。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long LL;
const int N = 1e5+10 , mod = 998244353;
inline int read()
{
	register int x = 0 , f = 0; register char c = getchar();
	while(c < '0' || c > '9') f |= c == '-' , c = getchar();
	while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0' , c = getchar();
	return f ? -x : x;
}
int n , cnt , all , rot , mxd;
LL ans;
int head[N] , siz[N] , mx[N] , vis[N] , f[N][2] , g[N][2];
struct edge{ int v , nex , c; } e[N << 1];
inline void addedge(int u , int v , int c) { e[++cnt].v = v; e[cnt].nex = head[u]; e[cnt].c = c; head[u] = cnt; return ; }

void getroot(int x , int fa)
{
	siz[x] = 1; mx[x] = 0;
	for(int i = head[x] , v; i ; i = e[i].nex)
	{
		v = e[i].v; if(v == fa || vis[v]) continue;
		getroot(v , x); siz[x] += siz[v];
		mx[x] = max(mx[x] , siz[v]);
	}
	mx[x] = max(mx[x] , all - siz[x]);
	if(mx[rot] > mx[x]) rot = x;
}

void calc(int x , int fa , int res , int cnt) // 缁熻x涓鸿矾寰勭殑涓€涓鐐圭殑鎯呭喌鏁?
{
	if(fa && (res == 0)) { cnt++; if(cnt >= 2) ans++; }
	for(int i = head[x] ; i ; i = e[i].nex) if(e[i].v != fa && !vis[e[i].v]) calc(e[i].v , x , res + e[i].c * 2 - 1 , cnt);
}

void dfs(int x , int fa , int res , int l , int r) // 璁板綍鎵€鏈夌殑 f , g
{
	if(l <= res && res <= r) { if(res >= 0) f[res][1]++; else g[-res][1]++; }
	else { if(res >= 0) f[res][0]++; else g[-res][0]++; }
	l = min(l , res); r = max(r , res); mxd = max(mxd , max(-l , r));
	for(int i = head[x] ; i ; i = e[i].nex) if(e[i].v != fa && !vis[e[i].v]) dfs(e[i].v , x , res + e[i].c * 2 - 1 , l , r);
}

void solve(int x)
{
	vis[x] = 1; calc(x , 0 , 0 , 0); mxd = 0; dfs(x , 0 , 0 , 1 , -1);
	ans += (LL)f[0][1] * (f[0][1] - 1) / 2; f[0][0] = f[0][1] = 0; // 
	for(int i = 1 ; i <= mxd ; ++i) ans += (LL)f[i][1] * g[i][0] + (LL)f[i][0] * g[i][1] + (LL)f[i][1] * g[i][1] , f[i][1] = f[i][0] = g[i][1] = g[i][0] = 0;
	for(int i = head[x] , v; i ; i = e[i].nex)
	{
		v = e[i].v; if(vis[v]) continue;
		mxd = 0; dfs(v , x , 2 * e[i].c - 1 , 0 , 0);
		ans -= (LL)f[0][1] * (f[0][1] - 1) / 2; f[0][0] = f[0][1] = 0;
		for(int j = 1 ; j <= mxd ; ++j) ans -= (LL)f[j][1] * g[j][0] + (LL)f[j][0] * g[j][1] + (LL)f[j][1] * g[j][1] , f[j][0] = f[j][1] = g[j][0] = g[j][1] = 0;
		all = siz[v]; rot = 0; getroot(v , x); solve(rot);
	}
}

int main()
{
	n = read();
	for(int i = 1 , u , v , c; i < n ; ++i) u = read() , v = read() , c = read() , addedge(u , v , c) , addedge(v , u , c);
	all = mx[0] = n; getroot(1 , 0); solve(rot);
	cout << ans << '\n';
	return 0;
}

应用2 , 树上的连通块

给出一棵 n 个点的树,每个点有物品重量 w 、体积 c 和数目 d 。要求选出一个连通子图,使得总体积不超过背包容量 m ,且总重量最大。求这个最大总重量。

扫描二维码关注公众号,回复: 11281212 查看本文章

先考虑以某个点为根,做一次dp , 先求出dfn序列, 如果选这个点,就可以用 dp[i + 1] 更新(dp的下标是dfn序列上的位置,不是具体的某个节点编号)然后二进制拆分枚举即可,若是不选 就要用\(dp[R[dfn[i]]]\) 更新, \(R[x]\) 是 x 的子树在dfn序列上的结束位置。

然后套上点分治即可。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long LL;
const int N = 520;
inline int read()
{
	register int x = 0 , f = 0; register char c = getchar();
	while(c < '0' || c > '9') f |= c == '-' , c = getchar();
	while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0' , c = getchar();
	return f ? -x : x;
}
int n , m , cnt , rot , ans , top , all;
int head[N] , w[N] , c[N] , d[N] , mx[N] , siz[N] , vis[N] , dfn[N] , R[N] , f[N][4010];
struct edge{ int v , nex; }e[N << 1];
inline void addedge(int u , int v) { e[++cnt].v = v; e[cnt].nex = head[u]; head[u] = cnt; return ; }

void getroot(int x , int fa)
{
	siz[x] = 1; mx[x] = 0;
	for(int i = head[x] , v; i ; i = e[i].nex)
	{
		v = e[i].v; if(v == fa || vis[v]) continue;
		getroot(v , x); siz[x] += siz[v]; mx[x] = max(mx[x] , siz[v]);
	}
	mx[x] = max(mx[x] , all - siz[x]);
	if(mx[rot] > mx[x]) rot = x;
}

void dfs(int x , int fa)
{
	dfn[++top] = x;
	for(int i = head[x] ; i ; i = e[i].nex) if(e[i].v != fa && !vis[e[i].v]) dfs(e[i].v , x);
	R[x] = top;
}

void solve(int x)
{
	vis[x] = 1; top = 0; dfs(x , 0);
	for(int i = 0 ; i <= top + 1 ; ++i) for(int j = 0 ; j <= m ; ++j) f[i][j] = 0;
	for(int i = top ; i ; i--)
	{
		for(int j = m ; j >= c[dfn[i]] ; --j) f[i][j] = f[i + 1][j - c[dfn[i]]] + w[dfn[i]];
		int t = d[dfn[i]] - 1;
		for(int j = 1 ; j <= t ; t -= j , j <<= 1)
			for(int s = m ; s >= j * c[dfn[i]] ; --s)
				f[i][s] = max(f[i][s] , f[i][s - j * c[dfn[i]]] + j * w[dfn[i]]);
		if(t)
			for(int s = m ; s >= t * c[dfn[i]] ; --s)
				f[i][s] = max(f[i][s] , f[i][s - t * c[dfn[i]]] + t * w[dfn[i]]);
		for(int j = 0 ; j <= m ; ++j) f[i][j] = max(f[i][j] , f[R[dfn[i]] + 1][j]);
	}
	ans = max(ans , f[1][m]);
	for(int i = head[x] ; i ; i = e[i].nex) if(!vis[e[i].v]) all = siz[e[i].v] , rot = 0 , getroot(e[i].v , 0) , solve(rot);
}

void Cls()
{
	cnt = 0;
	memset(head , 0 , sizeof head);
	memset(vis , 0 , sizeof vis);
}

void solve()
{
	n = read(); m = read();
	for(int i = 1 ; i <= n ; ++i) w[i] = read();
	for(int i = 1 ; i <= n ; ++i) c[i] = read();
	for(int i = 1 ; i <= n ; ++i) d[i] = read();
	int u , v;
	for(int i = 1 ; i <  n ; ++i) u = read() , v = read() , addedge(u , v) , addedge(v , u);
	all = mx[0] = n; rot = ans = 0; getroot(1 , 0); solve(rot);
	cout << ans << '\n';
	Cls();
}

int main()
{
	int T = read();
	while(T--) solve();
	return 0;
}
/*
2
3 2
1 2 3
1 1 1
1 2 1
1 2
1 3
3 2
1 2 3
1 1 1
1 2 1
1 2
1 3
*/

猜你喜欢

转载自www.cnblogs.com/R-Q-R-Q/p/12978082.html