zoukankan      html  css  js  c++  java
  • 点分治-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) << '
    ';
    	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 << '
    ';
    	return 0;
    }
    

    应用2 , 树上的连通块

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

    先考虑以某个点为根,做一次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 << '
    ';
    	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
    */
    
  • 相关阅读:
    PHP脚本如何正确启用sg11安全组件?
    android修改系统时系统黑屏时不进入休眠状态
    计算机自考视频汇总【福利资料】[转]
    解决MySql报错:1130
    “领导想提拔你,看的从不是努力
    interTbale ___AlterTable
    MySQL数据库管理系统概述
    《分布式任务调度平台XXL-JOB》
    mysql 在线文档
    Oracle19c 数据库在线文档
  • 原文地址:https://www.cnblogs.com/R-Q-R-Q/p/12978082.html
Copyright © 2011-2022 走看看