分治算法在树的路径问题中的应用
关于点分治的理解
#%%% qt666
点分治的基本思想如下:
考虑到树上的路径对于一个点来说只有两种情况:一是经过这个点,二是不经过这个点
对于不经过这个点的情况我们可以直接往下递归处理,主要问题就是解决经过这一个点的路径
我们知道:如果一条路径要经过这个点,那么他必然是由两条在这个点不同子树中到这个点的路径组合而成(这句话真的很重要)
对于这个问题一般又有两种思想:
1.运用容斥的思想,先把这个节点getdeep的时候搞出来的东西不管是否经过这个点(即不在同一子树)全部都计算,然后在减去子树中(即在同一子树)的这些答案
这样减剩下就是经过这个点的答案了(参考 树上的点对以及聪聪可可以及吕欣大爷的c题)
2.类似动态点分治的想法,我们直接对于每个点的子树的一个一个的做(组合或更新);比如说经常用一些堆,单调队列(貌似还有平衡树???)这些东西来实现子树贡献合并(参考所有动态点分治)
有一些人生经验(世间万物皆套路…):
1.跟倍数统计相关:开桶
例:聪聪可可的桶和吕欣c题的桶
2.如果发现主要是由于某个值的无序性导致无法确定某个值使得无法快速转移,那么就考虑能否sort后实现O(size)转移
例:树上的点对(权值和排序后线性扫),开店(按年龄排序后提取前缀和),吕欣的c题(按最大值排序后依次加入)
3.统计一些最值之类的东西:开堆(并考虑取最优和次优的组合之类的)
例:捉迷藏(取子树最优+次优合并),qtree5(堆维护最小值)
4.单调队列(做得少…)
例:重建计划(单调队列维护[L,R]间权值和最大的路)
#eg:
poj 1741 Tree
给一颗n个节点的树,每条边上有一个距离v(v<=1000)。定义d(u,v)为u到v的最小距离。给定k值,求有多少点对(u,v)使u到v的距离小于等于k。数据范围:n<=10000,k<2^31
#include
#include
#include
#include
using namespace std;
typedef long long LL;
void read(int &x) {
char c;bool flag = 0;
while((c=getchar())<'0'||c>'9') flag |= (c=='-');
x=c-'0';while((c=getchar())>='0'&&c<='9') x = x*10+c-'0';
flag?x=-x:x;
}
const LL inf = ~0u>>2;
#define N 210000
struct E {
int to,next,w;
E(int to=0,int w=0,int next=0):to(to),w(w),next(next){}
}g[N*2];
int fr[N],tot,siz[N],ms[N],done[N],d[N],root,sum,n,ans,k,dep[N],cnt;
void Add(int from,int to,int w) {
g[++tot] = E(to,w,fr[from]);
fr[from] = tot;
}
int getrt(int t,int fa) {
siz[t] = 1; ms[t] = 0;
for (int i = fr[t]; i; i = g[i].next) {
int to = g[i].to;
if(to == fa || done[to]) continue;
getrt(to,t); siz[t] += siz[to];
ms[t] = max(ms[t],siz[to]);
}
ms[t] = max(ms[t],sum-siz[t]);
if(ms[t] < ms[root]) root = t;
}
void getd(int t,int fa) {
dep[++cnt] = d[t];
for (int i = fr[t]; i; i = g[i].next) {
int to = g[i].to;
if(to == fa || done[to]) continue;
d[to] = d[t]+g[i].w;
getd(to,t);
}
}
int calc(int t,int v) {
d[t] = v; cnt = 0; getd(t,-1);
sort(dep+1,dep+cnt+1);
int r = cnt,l, tmp = 0;
for (l = 1; l <= cnt; l++) {
while(r>1 && dep[l]+dep[r]>k) --r;
if(r>l) tmp += r-l;
}
return tmp;
}
void slove(int rt) {
ans += calc(rt,0);
done[rt] = 1;
for (int i = fr[rt]; i; i = g[i].next) {
int to = g[i].to;
if(done[to]) continue;
ans -= calc(to,g[i].w);
sum = siz[to]; root = 0; getrt(to,-1);
slove(root);
}
}
void work() {
memset(fr,0,sizeof(fr[0])*(n+3)); tot = 0;
memset(done,0,sizeof(done[0])*(n+3)); ans = 0;
for (int i = 1,u,v,x; i < n; i++) {
read(u); read(v); read(x);
Add(u,v,x); Add(v,u,x);
}
sum = ms[0] = n;
root = 0; getrt(1,-1);
slove(root);
printf("%d\n",ans);
}
int main() {
while(~scanf("%d%d",&n,&k) && (n || k)) work();
return 0;
}
【bzoj2152】聪聪可可
聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。
#include
#include
#include
using namespace std;
typedef long long LL;
void read(int &x) {
char c;bool flag = 0;
while((c=getchar())<'0'||c>'9') flag |= (c=='-');
x=c-'0';while((c=getchar())>='0'&&c<='9') x = x*10+c-'0';
flag?x=-x:x;
}
const LL inf = ~0u>>2;
void FRE();
#define N 21000
struct E {
int to,next,w;
E(int to=0,int w=0,int next=0):to(to),w(w),next(next){}
}g[N*2];
int fr[N],tot,siz[N],ms[N],done[N],d[N],root,sum,n,ans;
int t[5];
void Add(int from,int to,int w) {
g[++tot] = E(to,w,fr[from]);
fr[from] = tot;
}
int gcd(int a,int b) {
return b==0 ? a:gcd(b,a%b);
}
int getrt(int t,int fa) {
siz[t] = 1; ms[t] = 0;
for (int i = fr[t]; i; i = g[i].next) {
int to = g[i].to;
if(to == fa || done[to]) continue;
getrt(to,t); siz[t] += siz[to];
ms[t] = max(ms[t],siz[to]);
}
ms[t] = max(ms[t],sum-siz[t]);
if(ms[t] < ms[root]) root = t;
}
void calc(int t,int fa) {
::t[d[t]]++;
for (int i = fr[t]; i; i = g[i].next) {
int to = g[i].to;
if(to == fa || done[to]) continue;
d[to] = (d[t]+g[i].w)%3;
calc(to,t);
}
}
void slove(int rt) {
t[0] = t[1] = t[2] = 0;
d[rt] = 0; calc(rt,-1);
ans += 2*t[1]*t[2] + t[0]*t[0]; t[0]*(t[0]-1)+t[0]
done[rt] = 1;
for (int i = fr[rt]; i; i = g[i].next) {
int to = g[i].to;
if(done[to]) continue;
t[0] = t[1] = t[2] = 0;
d[to] = g[i].w; calc(to,-1);
ans -= 2*t[1]*t[2] + t[0]*t[0];
sum = siz[to]; root = 0; getrt(to,-1);
slove(root);
}
}
int main() {
FRE();
read(n);
for (int i = 1,u,v,x; i < n; i++) {
read(u); read(v); read(x); x %= 3;
Add(u,v,x); Add(v,u,x);
}
sum = ms[0] = n;
root = 0; getrt(1,-1);
slove(root);
int Gcd = gcd(ans,n*n);
printf("%d/%d\n",ans/Gcd,n*n/Gcd);
return 0;
}
void FRE() {
assert(freopen("cckk.in","r",stdin));
assert(freopen("cckk.out","w",stdout));
}