【BZOJ1912】【Apio2010】巡逻 树上最长链(才不是树的直径呢)

链接:

#include <stdio.h>
int main()
{
    puts("转载请注明出处[vmurder]谢谢");
    puts("网址:blog.csdn.net/vmurder/article/details/45062689");
}

题解:

对于 k==0 的情况:

我们发现遍历一棵树最后回到原点,那么对于所有的边,我们都是走过去,再走回来。

答案 (n?1<<1)

对于 k==1 的情况

设每条边长度为1,然后树上找最长链,然后这条链走过去就不再一步步往回了,直接从链底连一条边去链顶,然后链中间连的那些点,直接走过去再走回来,它们那些边的答案是不变的。

答案 (n?1<<1)?(链长度)+1

可以证明不能减得更多啦。

z对于 k==2 的情况

设上一次的直径上的边的边长都为-1,因为再走一次会坑,然后树上找最长链。

答案 (n?1<<1)?(链1长度)+1?(链2长度)+1

可以证明不能减得更多啦。

我在这里就不贴出证明了,以免给读者养成不自己证明的惰性(其实是我这两天惰性有点大2333)

代码:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 101000
#define inf 0x3f3f3f3f
using namespace std;
struct Eli
{
    int v,len,next;
}e[N<<1];
int head[N],cnt;
inline void add(int u,int v)
{
    e[++cnt].v=v;
    e[cnt].len=1;
    e[cnt].next=head[u];
    head[u]=cnt;
}
int fa[N],pre[N];
int f1[N],f2[N],s1[N],s2[N];
int point;
bool cmp(int a,int b)
{
    int x=f1[a]+f2[a];
    int y=f1[b]+f2[b];
    return x>y;
}
void dfs(int x,int p)
{
    int i,u,v;
    s1[x]=s2[x]=x;
    for(i=head[x];i;i=e[i].next)
    {
        v=e[i].v;
        if(v==p)continue;
        fa[v]=x,pre[v]=i;
        dfs(v,x);
        if(f1[v]+e[i].len>f1[x])
        {
            f2[x]=f1[x];
            s2[x]=s1[x];
            f1[x]=f1[v]+e[i].len;
            s1[x]=s1[v];
        }
        else if(f1[v]+e[i].len>f2[x])
        {
            f2[x]=f1[v]+e[i].len;
            s2[x]=s1[v];
        }
    }
    if(cmp(x,point))point=x;
}
int n,m,p1,p2,x1,x2;
int main()
{
//  freopen("test.in","r",stdin);

    int i,j,k;
    int a,b,c;
    int ans;

    scanf("%d%d",&n,&m),ans=n-1<<1;
    for(cnt=i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    f1[0]=f2[0]=-inf;
    dfs(1,0);
    ans-=f1[point]+f2[point]-1;
    if(m==1)printf("%d\n",ans);
    else {
        memset(f1,0,sizeof f1);
        memset(f2,0,sizeof f2);
        for(a=s1[point];a!=point;a=fa[a])e[pre[a]].len=-1,e[pre[a]^1].len=-1;
        for(a=s2[point];a!=point;a=fa[a])e[pre[a]].len=-1,e[pre[a]^1].len=-1;
        dfs(1,0);
        ans-=f1[point]+f2[point]-1;
        printf("%d\n",ans);
    }
    return 0;
}

不是说有一种bfs求树的直径的方法么?

这里提供一下代码:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 101000
using namespace std;
struct Eli
{
    int v,len,next;
}e[N<<1];
int head[N],cnt;
inline void add(int u,int v)
{
    e[++cnt].v=v;
    e[cnt].len=1;
    e[cnt].next=head[u];
    head[u]=cnt;
}
queue<int>q;
int f[N],pre[N],fa[N];
bool vis[N];
int bfs(int root)
{
    int i,u,v,ans,x=-1;
    memset(vis,0,sizeof vis);
    q.push(root),f[root]=0,vis[root]=1;
    while(!q.empty())
    {
        u=q.front(),q.pop();
        if(x<f[u])x=f[u],ans=u;
        for(i=head[u];i;i=e[i].next)
        {
            if(!vis[v=e[i].v])
            {
                f[v]=f[u]+e[i].len;
                vis[v]=1;
                fa[v]=u;
                pre[v]=i;
                q.push(v);
            }
        }
    }
    return ans;
}
int n,m,p1,p2,x1,x2,root;
int main()
{
    freopen("test.in","r",stdin);

    int i,j,k;
    int a,b,c;

    scanf("%d%d",&n,&m);
    for(cnt=i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    root=bfs(1),p1=bfs(root),x1=f[p1];
    if(m==1)printf("%d\n",(n-1)*2-x1+1);
    else {
        while(p1!=root)e[pre[p1]].len=-1,e[pre[p1]^1].len=-1,p1=fa[p1];
        root=bfs(1),p2=bfs(root),x2=f[p2];
        printf("%d\n",(n-1)*2-x1+1-x2+1);
    }
    return 0;
}

/*
    反例:
5 2
1 2
1 3
3 4
3 5
    错误原因:
    两遍bfs时有多个点距离根距离相同,而最终答案却不同Qwq
    总之你只要敢写我就敢对着代码hack。
*/

我可没说它AC了啊

时间: 2024-08-08 17:50:03

【BZOJ1912】【Apio2010】巡逻 树上最长链(才不是树的直径呢)的相关文章

hdu 2196(树的最长链)

题意:输出从一颗树中所有结点出发可以走的最长的路. 思路:先找到树上最长链然后判断两个端点中到每个结点远的距离就是答案. 代码如下: 1 #include <stdio.h> 2 #include <string.h> 3 #include <iostream> 4 #include <algorithm> 5 #include <vector> 6 #include <queue> 7 #include <set> 8

BZOJ1912 [Apio2010]patrol 巡逻

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转载请注明出处,侵权必究,保留最终解释权! Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n). Output 输出一个整数,表示新建了K 条道路后能达到的最小

【GDOI2020模拟01.16】树上的鼠 (博弈+长链剖分优化dp)

https://gmoj.net/senior/#contest/show/2989/2 思考什么时候先手会赢. 一开始双方都不会希望走到直径的端点上,因为那样对方就可以走直径而使自己输掉. 删掉直径的端点,考虑剩下的树的子问题. 如果又走到端点去了,对面就走到另外一个端点,那我就走到下一层的直径端点去了. 所以大家一直都不想走到直径端点. 一直删,如果最后只剩1一个点,说明先手必败,否则先手必胜. 如果是一条链,就是链的两边的长度不等先手就必胜. 如果是一棵树,考虑随便找一条直径,每次删去它的

长链剖分随想

之前写了那么长一篇Blog-现在不如写篇小短文-说一下另一种树链剖分方法--长链剖分的事情.它可以比重链剖分更快地完成一些东西. 树链剖分的原始版本重链剖分非常经典,这里就不从头介绍了. 原本的剖分方法是按照子树大小剖分,与子树点数最多的儿子连成链,所以叫做重链剖分-然后显然就有一个点到根的路径上至多$O(\log n)$条轻边这个性质(因为沿着轻边走,每次子树大小一定小于父亲的一半).有了这个性质就可以做各种路径相关的查询,暴力每次跳到重链开头就好- 而在一些问题里,有这么一种奇妙的剖分方式可

洛谷 P3629 【[APIO2010]巡逻】

题目在这里 这是一个紫题,当然很难. 我们往简单的想,不建立新的道路时,从1号节点出发,把整棵树上的每条边遍历至少一次,再回到1号节点,会恰好经过每条边两次,路线总长度为2(n-1),根据树的深度优先遍历思想,很容易证明这个结论,因为每条边必然被递归一次,回溯一次. 建立1条新道路之后,因为新道路必须恰好经过一次(0次,2次都不可以),所以在沿着新道路(x,y)巡逻之后,要返回x,就必须沿着树上从y到x的路径巡逻一遍,最终形成一个环.与不建立新道路的情况相结合,相当于树上x与y之间的路径就只需经

Luogu P3629 [APIO2010]巡逻【题解】树的直径

Luogu P3629 [APIO2010]巡逻 ### 树的直径 题面链接 看题就知道应该是连树的直径,也就是最长链 \(ans=2(n-1)-l1+1\) 但是\(k\le2\) 当他是\(2\)的时候怎么处理??? 只好再跑一遍求树的直径 我们先把之前求出的\(l1\)的所有边变为\(-1\) 之后再求 \(ans=2(n-1)-(l1-1)-(l2-1)\) 要是有重叠也不用特殊处理 因为\(l1\)取反了之后再减去\(l2\)就相当于加回来了,还是两次 所有这一个公式就解决了 代码如下

Codefroces Gym 100781A(树上最长路径)

http://codeforces.com/gym/100781/attachments 题意:有N个点,M条边,问对两两之间的树添加一条边之后,让整棵大树最远的点对之间的距离最近,问这个最近距离是多少. 思路:一开始看成只有两个连通块,后来才注意到是多个连通块.DFS搜树上最长路径.答案有三种:第一种是添加了边之后,树的最长路径还是原来子树的路径,第二种是对子树长度进行排序后,两个最长的距离分别除以2向上取整后加上1.第三种比较难注意到,就是第二第三长的分别除以2向上取整后加上2,因为可能隔着

【BZOJ-1912】patrol巡逻 树的直径 + DFS(树形DP)

1912: [Apio2010]patrol 巡逻 Time Limit: 4 Sec  Memory Limit: 64 MBSubmit: 1034  Solved: 562[Submit][Status][Discuss] Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n). Output 输出一个整数,表示新建了K 条道路后能达到的最小巡逻距离.

比特币如何达成共识 - 最长链的选择

比特币没有中心机构,几乎所有的完整节点都有一份公共总帐本,那么大家如何达成共识:确认哪一份才是公认权威的总账本呢? 为什么要遵守协议 这其实是一个经济问题,在经济活动中的每个人都是自私自利的,追求的是利益的最大化,一个节点工作量只有在其他的节点认同其是有效的(打包的新区块,其他的节点只有验证通过才会加入到区块链中,并在网络上传播),才能够过得收益,而只有遵守规则才会得到其他的节点认同.因此,基于逐利,节点就会自发的遵守协议.共识就是数以万计的独立节点遵守了简单的规则(通过异步交互)自发形成的.