点分治-summmy
点分治就是在树上以重心分治,每次摘除重心,把树分成若干的siz较小的快,之后再递归处理。
主要有两种写法,
一种是在根处统计所有到根的信息,然后两两合并,再减去强制经过某个节点的贡献,
另一种是强制进入某个子树,得到这个子树的信息,再用这份信息与之前的合并。
应用1 , 有关树上的路径
BZOJ 2599 :[IOI2011]Race Link
给一棵树,每条边有权.求一条简单路径,权值和等于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
*/
BZOJ 3697 采药人的路径 Link
给出一棵 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 , 树上的连通块
BZOJ4182 Shopping Link
给出一棵 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
*/