博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
点分治——树上路径统计
阅读量:6150 次
发布时间:2019-06-21

本文共 5050 字,大约阅读时间需要 16 分钟。

点分治:

一种分治的方法,一般用于(在不修改情况下),处理两点树上的两点之间的路径的问题。

每次从当前的子图中找到重心,即点分治“点”的含义。

以重心为当前的根节点,查找一切经过重心的路径,更新产生的贡献。

 

查找经过当前重心的路径的贡献,一般有两种方法:

1.树形背包思想

每次用当前子树和之前子树搭配找到贡献,然后把当前子树加入到之前子树集合中去。

为了计数的时候不重不漏,可以这样做:

把根(dis=0)加入集合。

之后就不断找子树,统计贡献,加入集合,直到子树循环完毕为止。

这样,既统计了根和子树的路径,也统计了子树之间跨根的路径。

 

2.容斥思想

我们每一层是要处理经过根的所有简单路径(这里是不经过重点重边)贡献。

一棵子树内部的点到根,再从根回到子树内部的路径一定不合法(一定有重边重点)。

我们可以先把所有的点的dis值依次和 前面点统计、然后加入一个集合里。

但是这样显然会有上面的不合法的情况,所以,在这一次分治的最后,

循环每个子树,

把每个子树节点之间的两两贡献减去,方法同上(统计,加入集合)。

 

例题:

一、

模板题:

Description:

给定一棵有n个点的树

m次询问树上距离为k的点对是否存在。

Hint:

对于30%的数据n<=100

对于60%的数据n<=1000,m<=50

对于100%的数据n<=10000,m<=100,c<=1000,K<=10000000

Solution:

在每一个区间里:div(x)表示,处理x所在的块的贡献。

个人比较喜欢用树形背包的思想,处理当前子树和之前子树结合产生的贡献。

0.开一个O(k)的数组exi,表示,距离根节点为k的点是否存在。

1.dfs1(x)找到子图的rt,用一个栈记录所有当前块的点。member

2.dfs2(x)找到子图的每个子树的dis,即到根的距离。再用一个栈记录访问的节点。

回溯时,如果x==rt,

①那么从栈里循环 ,找到another=k-dis[in[j]]表示,另一半需要的距离长度。

如果exi[another]=1,那么就找到了k

②之后,不断退栈,把exi[dis[in[j]]赋值为1。

注意,必须把所有的1的查找先进行完,再进行②的更新。因为是这个子树和之前子树的路径。

同一个子树之间的点在这一层不会产生贡献

3.把member栈的元素不断退栈,把exi[dis[in[j]]赋值为0。保证之后再查到的路径一定是过rt'的合法路径。

4.封锁rt节点,之后不能再访问回来。

5.分治下去,找到没有“封锁”的点,令块大小totsz=sz[y],div(y)

 

最后也不用判断totsz==1,一定全部会把回路都封锁住。直接就返回了。

代码:

注意:1.dis[in[j]]可能大于k,就RE;

2.another可能小于0,又RE

#include
using namespace std;const int N=10000+10;const int M=10000000+3;const int inf=0x3f3f3f3f;int n,m;int k;//Kbool exi[M];int sta[N],top;//stack to memorize exiint in[N],up;//stack to memorize dfs2struct node{ int nxt,to; int val; }e[2*N];int hd[N],cnt;void add(int x,int y,int z){ e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].val=z; hd[x]=cnt;}int dis[N];int rt;bool no[N];int sz[N];int mxs[N];int minsize;int totsz;bool fl;//find ?void dfs1(int x,int fa){ sz[x]=1; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(no[y]) continue; if(y==fa) continue; dfs1(y,x); sz[x]+=sz[y]; mxs[x]=max(mxs[x],sz[y]); } mxs[x]=max(mxs[x],totsz-sz[x]); if(minsize>mxs[x]){ minsize=mxs[x]; rt=x; }}void dfs2(int x,int fa,int d){ dis[x]=d; in[++up]=x; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(no[y]) continue; if(y==fa) continue; dfs2(y,x,d+e[i].val); if(x==rt){ int kk=up; while(kk){ int ano=k-dis[in[kk]]; if(ano>=0&&ano<=k){ if(exi[ano]) fl=true; } kk--; } while(in[up]!=rt){ sta[++top]=in[up]; if(dis[sta[top]]<=k) exi[dis[sta[top]]]=1; up--; } } }}void div(int x){ if(fl) return; up=0;top=0; minsize=inf; dfs1(x,0);//find rt sta[++top]=rt; exi[0]=1; dfs2(rt,0,0); while(top){ if(dis[sta[top]]<=k) exi[dis[sta[top]]]=0; dis[sta[top]]=0; mxs[sta[top]]=0; top--; } no[rt]=1; for(int i=hd[rt];i;i=e[i].nxt){ int y=e[i].to; if(no[y]) continue; totsz=sz[y]; div(y); }}int main(){ scanf("%d%d",&n,&m); int x,y,z; for(int i=1;i<=n-1;i++){ scanf("%d%d%d",&x,&y,&z); add(x,y,z);add(y,x,z); } for(int i=1;i<=m;i++){ scanf("%d",&k); fl=false; totsz=n; minsize=inf; memset(mxs,0,sizeof mxs); memset(dis,0,sizeof dis); memset(no,0,sizeof no); memset(sz,0,sizeof sz); div(1); if(fl) printf("AYE\n"); else printf("NAY\n"); } return 0;}
模板

 

二、

[国家集训队]聪聪可可

题目描述

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。

他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。

聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

输出格式:

以即约分数形式输出这个概率(即“a/b”的形式,其中a和b必须互质。如果概率为1,输出“1/1”)。

【数据规模】

对于100%的数据,n<=20000。

 

Solution:

框架大致同上。

统计子树里mod 3=0 mod3=1 mod3=2的点的个数。

再用一个sum[3]表示,之前所有的子树点里mod3余数的点的个数。

每次rt回溯的时候,更新:ans+=sum[0]*sub[y][0],ans+=sum[1]*sub[y][2],ans+=sum[2]*sub[y][1]

然后跟新sum

只需要一个栈,存储member就可以。sub可以dp得到。

Code:

#include
using namespace std;const int N=20000+10;const int inf=0x3f3f3f3f;int n;int gcd(int a,int b){ return b?gcd(b,a%b):a;}struct node{ int nxt,to,val;}e[2*N];int hd[N],cnt;int rt;void add(int x,int y,int z){ e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].val=z; hd[x]=cnt;}int ans;int sum[3],sub[N][3];int dis[N],sz[N],mxs[N];bool vis[N];int totsz,minsz;int sta[N],top;void dfs1(int x,int fa){ sz[x]=1;sta[++top]=x; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; if(vis[y]) continue; dfs1(y,x); sz[x]+=sz[y]; mxs[x]=max(mxs[x],sz[y]); } mxs[x]=max(mxs[x],totsz-sz[x]); if(mxs[x]
聪聪可可

 

三、

 

四、

 

 

点分治有什么好处?我们为什么不直接用树形dp?

它多用了一个logn的代价,使得我们每次面对的都是过重心rt的路径。

这样,我们可以灵活用子树来处理。

而树形dp必须一次考虑所有过x的所有路径。必须还要多处理一个“和x有关”的信息,多了O(n)的时空。

 

点分治由于同一层不用考虑其他路径,所以复杂度和思维含量大大降低。

(分治都是这样干的。。)

 

进阶:

转载于:https://www.cnblogs.com/Miracevin/p/9475786.html

你可能感兴趣的文章
下一步工作分配
查看>>
Response. AppendHeader使用大全及文件下载.net函数使用注意点(转载)
查看>>
Wait Functions
查看>>
代码描述10313 - Pay the Price
查看>>
jQuery最佳实践
查看>>
centos64i386下apache 403没有权限访问。
查看>>
vb sendmessage 详解1
查看>>
jquery用法大全
查看>>
PC-BSD 9.2 发布,基于 FreeBSD 9.2
查看>>
网卡驱动程序之框架(一)
查看>>
css斜线
查看>>
Windows phone 8 学习笔记(3) 通信
查看>>
重新想象 Windows 8 Store Apps (18) - 绘图: Shape, Path, Stroke, Brush
查看>>
Revit API找到风管穿过的墙(当前文档和链接文档)
查看>>
Scroll Depth – 衡量页面滚动的 Google 分析插件
查看>>
Windows 8.1 应用再出发 - 视图状态的更新
查看>>
自己制作交叉编译工具链
查看>>
Qt Style Sheet实践(四):行文本编辑框QLineEdit及自动补全
查看>>
[物理学与PDEs]第3章习题1 只有一个非零分量的磁场
查看>>
深入浅出NodeJS——数据通信,NET模块运行机制
查看>>