您的位置:首页 > 其它

点分治专题——bzoj 1468 &bzoj 2152 题解

2014-05-13 20:50 330 查看
【2017.6.22更新】博客坟了很久抱歉,看了看评论有种口诛笔伐之感233。

现在代码风格变了很多,也变得更加精确了。以前的程序的确是有问题的。

除了一楼说的问题,还有一个很严重的问题(以前数据太弱了233):

某一次(方便说明假设是第一次)去找root的时候,会存在一块“按我们搜索顺序位于最终root祖先“的树T;继续递归每一块树的时候,我们需要获得它们的大小;而T的“size”其实是不对的,我们无法准确获知它的大小。

不嫌麻烦的话,可以特判当前去递归的这块树是不是T,如果是的话它的真实点数其实是  当前树的节点数-size[x](x是目前的root)但是这样毕竟比较繁琐、不优美。

有一个很好的解决方法:把Findroot的过程写成非递归的形式,现在正确的代码放在下面。(风格猎奇233)

#define For(x) for (int i=End[x],y;i;i=Next[i]) if (!vis[y=Go[i]]&&y!=Fa)
int Root(int st){
int h=0,t=1;flag[Q[1]=st]=1;
while (h<t){
int x=Q[++h],Fa=0;
For(x) if (!flag[y]) flag[Q[++t]=y]=1;
}int ret=0;son[0]=t+1;
for (int i=t;i;i--){
int x=Q[i],Fa=0;size[x]=1;son[x]=0;flag[x]=0;
For(x) if (!flag[y]) size[x]+=size[y],son[x]=max(son[x],size[y]);
son[x]=max(son[x],t-size[x]);
if (son[x]<son[ret]) ret=x;
}return ret;
}
void Divide(int x){
vis[x]=1;int Fa=0;
For(x) DFS(y);     //DFS的话是具体的操作
For(x) Divide(Root(y));
}


-------------------------------------以下是原来的内容(代码已删除)-------------------------------------------------

【前言】最近一直在忙着学算法,但是效果似乎不是很好。前段时间的树剖也快忘了= =。树套树没熟练,就开始写主席树了= =。更别说本身就不是很懂的莫比乌斯反演了。~~决定好好复习一下。

【点分治的作用】套用SYC大神的话说是:用来解决树上路径点权统计问题。

【大致流程】

①找出这颗树的重心。

②统计经过这个重心的答案

③用重心把树割开

④对每个“小树”做同样的事

【Q1——重心】其实找重心再进行计算只是为了不被卡链。什么是重心?就是当前树中的一个点K,使得MAX(SON[K])最小。SON[K]指的是以K为根的情况下某个孩子的点数。感性的想,重心在树的中间位置。

【Q2——统计答案】假设我们已经确定了一个重心K。我们先计算和K有关的(即经过K的路径或是以K为起点/终点的路径)答案个数,然后再递归每一棵小树,同样进行找重心、计算的过程。而且有些时候,我们计算出有关K的答案是有重复的,因此我们在递归的时候还要减去重复的(容斥原理)。

【Q3——边界】怎么使得某棵小树和其他树分开呢?其实很简单,我们可以开个1..n的布尔数组,表示x是否成为过重心。如果成为过,我在搜索的时候就可以直接退出了。

【T1——BZOJ1468】

给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K

n<=40000

【分析】真是一道经典题,POJ,USACO上都有。我们每次找到重心K后,把每个点到K的相对距离都算出来。然后把他们存到一个数组里去,用O(NLOGN)的快排和O(N)的扫描算出点对数。但是这样是有重复的,如图。



这样的话,设K=1,那么3和4这一条(假设3-2-1-2-4是符合要求的)就重复算了。于是我们在2--3--4这棵树中,把3-2-4这条路给删掉。怎么实现呢?我们调用左下角的小树,给d[3]一个初始值path[1][2]。这样我们可以得到d[3]=path[1][2]+path[2][3],d[4]=path[1][2]+path[2][4]。如果3-2-1-2-4符合要求,这当然也符合。这样就可以成功地删掉了。

【T2——BZOJ2152】

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

输入的第1行包含1个正整数n。后面n-1行,每行3个整数x、y、w,表示x号点和y号点之间有一条边,上面的数是w。

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

n<=20000。

【分析】可能这道更简单吧,不用快排,也不用去重。我们对于当前的树G,若找到的重心是K,我们就从K出发,搜索与K相邻的点,寻找边长的余数分别是是0,1,2的情况数,然后分情况讨论。











                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  bzoj 题解 点分治