从头开始的点分治

今天发现之前有篇随笔忘发布了……

TREE

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

【分析】考虑树根rt,可以处理出rt到其子树中的节点的距离,设为dis[]。利用dis数组的值,我们可以轻易地得到所有“端点在rt的子树中,长度≤k的,且经过rt的路径”数量(具体做法是将dis的值排序,然后双指针扫描)。要去除其中的非简单路径数量,只需要对rt的每个“儿子的子树”做一遍类似的过程(区别在于儿子的子树中的路径长度需要≤k-len[rt,儿子]),减去新得到的数量。将这一过程称为s()。

再考虑计算“不经过rt的路径”数量,显然此时两个端点必处于rt的某个儿子的子树中,因此我们递归处理“儿子的子树”即可。这一过程恰属“分治”范畴。

稍微动脑就知道这种做法很不科学。(举个例子:链式图)为了得出更稳得算法,我们先需要明确“所有递归深度相同的s()过程的总时间复杂度不超过O(n+nlogn)”。因此一个有效的优化方向是减少递归的层数。我们在一棵树中选出一个节点使得以之为根时,各子树大小尽量均匀(最大的子树的小尽量小,即该节点为树的重心),就能很好地达到这一目的。可以发现,这样做的递归层数是对数级别的。

【实现】

int rt,sum,fiz[N],siz[N];
int dis[N],tmp[N],top;
bool ban[N];
long long ans;

void addEdge(int x,int y,int w) {
    static int cnt=0;
    to[++cnt]=y;
    len[cnt]=w;
    last[cnt]=head[x];
    head[x]=cnt;
}
void getRoot(int x,int pa) {
    siz[x]=1;
    fiz[x]=0;
    for(int i=head[x]; i; i=last[i]) {
        if(to[i]==pa||ban[to[i]]) continue;
        getRoot(to[i],x);
        siz[x]+=siz[to[i]];
        fiz[x]=max(fiz[x],siz[to[i]]);
    }
    fiz[x]=max(fiz[x],sum-siz[x]);
    if(fiz[x]<fiz[rt]) rt=x;
}
void getDis(int x,int pa) {
    tmp[++top]=dis[x];
    for(int i=head[x]; i; i=last[i]) {
        if(to[i]==pa||ban[to[i]]) continue;
        dis[to[i]]=dis[x]+len[i];
        getDis(to[i],x);
    }
}
int calc(int x,int preLen) {
    top=0;
    dis[x]=preLen;
    getDis(x,0);
    sort(tmp+1,tmp+top+1);
    int ret=0,l=1,r=top;
    while(l<r) {
        if(tmp[l]+tmp[r]<=k) ret+=r-l, l++;
        else r--;
    }
    return ret;
}
void solveAt(int x) {
    ban[x]=true; // 把以后的过程中不会再涉及到的点ban掉。
    ans+=calc(x,0);
    for(int i=head[x]; i; i=last[i]) {
        if(ban[to[i]]) continue;
        ans-=calc(to[i],len[i]);
        rt=0;
        sum=siz[to[i]];
        getRoot(to[i],0);
        solveAt(rt);
    }
}

int main() {
    scanf("%d",&n);
    for(int x,y,w,i=n; --i; ) {
        scanf("%d%d%d",&x,&y,&w);
        addEdge(x,y,w);
        addEdge(y,x,w);
    }
    scanf("%d",&k);
    sum=n;
    fiz[0]=0x3f3f3f3f;
    getRoot(1,0);
    solveAt(rt);
    printf("%lld\n",ans);
    return 0;
} 

[IOI2011] Race

【题意】给一棵树,每条边有权。求一条简单路径,权值和等于 k(一组询问),且边的数量最小。
【分析】tmp再记录个所用边的数量。每次calc维护bin[i]表示i条边、权值和为k的路径条数。最后输出最小的i使得bin[i]>0即可。
【实现】

bool youBiYaoTieMa=false; 

模板-点分治1

【题意】多次询问,判断是否存在路径长度为k的点对。
【分析】第一题的变形,因为是判断可行性,就不需要容斥了,代码也很好写。
【实现】

// ques[i]是第i个询问的内容。
// ans[i]是第i个询问的答案。
void calc(int x) {
    int num=0;
    for(int i=head[x]; i; i=last[i]) {
        if(ban[to[i]]) continue;
        tot=0;
        dis[to[i]]=len[i];
        getDist(to[i], x);
        for(int j=tot; j; --j) for(int k=1; k<=m; ++k)
            if(ques[k]>=tmp[j]) ans[k]|=mem[ques[k]-tmp[j]];
        for(int j=tot; j; --j)
            all[++num]=tmp[j], mem[tmp[j]]=1;
    }
    for(int i=num; i; --i) mem[all[i]]=0;
}
void solveAt(int x) {
    ban[x]=1;
    mem[0]=1;
    calc(x);
    for(int i=head[x]; i; i=last[i]) {
        if(ban[to[i]]) continue;
        sum=size[to[i]];
        mxn[rt=0]=2e9;
        getRoot(to[i], 0);
        solveAr(rt);
    }
}

聪聪可可

【题意】给你一棵树,求边权和为3的倍数的路径数目与总路径数目的最简分数表达,路径的端点可以重合。
【实现】

bool youBiYaoTieMa=false;

快递员

【题意】不好概括自个儿读吧。
【分析】任然是点分治。考虑在树上选个点rt作为根,并且快递中心就选这儿。计算出所有配送的代价(2*两段之和),设他们的最大值为Max。若此时存在下列情况时,可以判定Max已经为最优解。

1)存在代价为Max的配送(u,v)且uv分别属于rt的不同的两个“儿子的子树”。
2)存在代价为Max的配送(u1,v1)(u2,v2)且u1u2分别属于rt的不同的两个“儿子的子树”。
3)存在代价为Max的配送(u1,v1)(u2,v2)且v1v2分别属于rt的不同的两个“儿子的子树”。

但是若1)不存在,2)、3)不就是一种情况了吗,滑稽。 概括一下就是当所欲代价为Max的配送的端点所属于的“儿子的子树”不唯一,则已达到最优解,证明就上边那三种情况。

如果都不满足的话,那么更优的选点应在Max的配送(u,v)的u(=v)所属于的那个“儿子的子树”里。分治下去就好。

【实现】传送门

[BJOI2017]树的难题

【题意】不好概括自个儿读吧。
【分析】按照常规思路,选一个点x作为分治中心,拼接x出发到子树各点的路径。对于拼接时两段接口处(即x连出的那条边,若没有,设为0号边:颜色为0,长度为0,到达0号儿子)颜色的影响,可以记录每段的路径权值、边数以及该段的接口,将所有的路径以接口颜色为第一关键字,接口编号为第二关键字排序。显然,对于同一接口的路径必为连续的一段序列。这样枚举每个路径,找到之前出现的符合边数和的要求的最大的路径权值即可。用两颗线段树维护,一棵维护与本路径不同颜色的,一颗维护与本路径颜色相同但接口不同的(因为简单路径的拼接要满足两个端点不在同一个子树内)。
【实现】传送门

原文地址:https://www.cnblogs.com/nosta/p/10225550.html

时间: 2024-10-08 18:37:33

从头开始的点分治的相关文章

【总结】静态点分治

这是一篇关于静态点分治的总结 点分治是什么? 就是一种统计答案的方式,用在树上 想要从头开始学点分治的话,可以点这里 点分治的标志:树上处理点对问题大部分用点分(当然还要复杂度允许) 对于一个树上点对问题,一个点只会有两种情况,一种是选,一种是不选 那么点分治就是按照这种思路去完成答案的统计,然后每次都找的是重心(size最大的子树的size最小的点),保证复杂度 一些可能有用的东西 点分治并不模板,而且还很思维.分治好后,统计答案还是需要思考的,而不是万能板子,所以没有捷径 这里只是一些去重的

凸包算法-GrahamScan+暴力+分治

RT.求平面上点集的凸包. 1. GrahamScan算法,<算法导论>上的例子,先找到y最小的点O,以O建立极坐标,其它点按极角排序后再从头开始扫描(配合stack实现). 2.BruteForce算法,依赖定理:如果一个点在平面上某三个点组成的三角形内,那么这个点不可能是凸包上的点. 所以暴力的思路是平面上的点每4个进行枚举,并判断是否满足定理,若满足,则删除这个点继续找:一直找到没有满足定理的点为止.. 3.DivideAndConquer思路:有很多种,这里我实现的只是严格意义上最后一

时间机器(CDQ分治)

题解:第一眼瞄过去以为是个可持久化线段树(看来我还真菜),这题其实解法有很多,我就说说在考场上想到的CDQ分治. 首先这道题要按操作一步一步去做,而且操作还有时间的这个限制,一个操作i对另一操作j有贡献当且仅当该操作i在操作j前,且操作i的时间在操作j前,这是一个经典的二维偏序.我们把操作分成l,mid和mid+1,r两部分(这个按输入顺序分),先各自处理(l,mid)和(mid+1,r),然后处理l,mid对mid+1,r的贡献. 如何处理l,mid对mid+1,r的贡献呢,我们在此之前已经各

LightOJ1257 Farthest Nodes in a Tree (II)(树的点分治)

题目给一棵树,边带有权值,求每一点到其他点路径上的最大权和. 树上任意两点的路径都可以看成是经过某棵子树根的路径,于是果断树分治. 对于每次分治的子树,计算其所有结点到根的距离:对于每个结点,找到另一个离根最远的且与该结点路径过根的结点,二者的距离和就是这个点在过这棵子树的根能到的最远距离. 现在问题就是怎么比较快地找到这另一个最远距离的点..两点路径过根,说明两点间不存在一点是另一点的祖先..我一开始还想用DFS序+线段树来着..想了想,想出了线性的算法: 记录每个结点属于根的哪个儿子,把当前

点分治练习: boatherds

[题面] 求一颗树上距离为K的点对是否存在 输入数据 n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径 接下来m行每行询问一个K 输出数据 对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号) 数据范围 对于30%的数据n<=100 对于60%的数据n<=1000,m<=50 对于100%的数据n<=10000,m<=100,c<=1000,K<=10000000 [思路] 树分治. 离线存储m个询问.分治判断该m个询问

HDU 5269 &amp;&amp; BestCoder #44 1002 ZYB loves Xor I (分治)

题目地址:HDU 5269 比赛的时候想到了分治的思路,但是脑残了.,.写麻烦了...调了好久也没调出来..(分治写的太少..)赛后优化了一下..就过了.. 我的思路是先排序,排序是按照的将每个数字的二进制表示倒过来的字典序从大到小排,比如样例2中的2,6,5,4,0,二进制分别是010,110,101,100,000,排序之后是 101 110 010 100 000 这样的话就把后缀相同的都给放在一块了.其实也相当于字典树,不过比赛的时候没想到字典树,只想到了分治.. 然后从末位开始找,对所

2015从头开始配置Hibernate-------Hibernate异常集锦

Eclipse不像是MyEclipse,它的Hibernate的使用需要进行从头配置.配置的基本方法就是把相应的jar包复制到WEB-INF/lib目录下.这些jar包,我是从 "http://ncu.dl.sourceforge.net/project/hibernate/hibernate3/3.6.10.Final/hibernate-distribution-3.6.10.Final-dist.zip" 这个地址下载的. 在"hibernate-distributio

算法基础:分治模式,归并排序ΘΘΘΘΘΘ知识小结

1.分治模式在每层递归时都有三个步骤:分解,解决,合并 2.归并排序算法完全遵循分治模式: 分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列 解决:使用归并排序递归的排序两个子序列 合并:合并两个已排序的子序列以产生已排序的答案 3.分析分治算法所需要的时间计算: 假设T(n)是规模为n的一个问题的运行时间,若问题足够小,如对某个常量c,n≦c,则直接求解需要常量时将,我们将其写作Θ(1).假设吧原问题分解成a个子问题,每个子问题的规模是原问题的1/b(对归并排序,a和b都为2,然

Codeforces 97B Superset 平面分治

题目链接:点击打开链接 题意: 给定一个点集 添加一些点后再把这个点集输出来. 添加完点后使得对于点集内任意2个点都满足下面2条中至少一条 1.在同一水平线上或在同一垂直线上 2.所围成的矩阵里有其他点. 思路: 平面分治 先把点按x轴排序,然后找到中间的点,做一条直线 x = a[mid].x; 然后把所有点都投影到这条直线上,那么对于左边的点就不需要再和右边的进行匹配了. #pragma comment(linker, "/STACK:1024000000,1024000000")