【BZOJ1912】【codevs1613】巡逻

传送门1
传送门2

思路:
我也不知道该说是什么算法……
显然k=1时答案就是求树的直径d
然后答案就是 2nd+1
然后k=2当时的我并不会怎么做……
觉得是在k=1的基础上再在各个外向树上再找一发最大直径
然后就感人肺腑地过了21个点(总共30个点)……
容易发现这样贪心是错误的
因为答案可能是两棵外向树相互连接
那怎么办?
我们画个非常抽象的图来感受一下(高能)
这里写图片描述
上面就是连完两条边后图中存在的两个环的情况(没有画出环上的树)
红色的边就是两个环重复的边
我们发现红色的边是必然要走两次的,而环上的其他边只用走一次
也就是说这些红色的边和原来树上的边在本质上没有区别,都是要被计算进答案两次的
我们这样来考虑
k=0时答案是 2(n1) ,这是显然的
当加入一条边时,答案本会变成 2n ,但树上会形成一个环,这个环上的每条边从走两次变成了走一次,对答案的贡献是-1,设这个环共有x条边(包括加入的那条边),答案就是 2nx ,由于 x=d1 ,所以k=1时答案就是 2nd+1
当在加入一条边的基础上再加入一条边,又会形成一个新的环,这两个环共有的每条边从走一次变成了走两次,对答案的贡献就成了1,设第一个环有a条边,第二个环有b条边,它们共有的边是c条,那么答案就是 2n+2(ac)(bc)
实际上也就是说,在加入第二条边时,每条非环边的贡献是-1,每条环上边的贡献是1,我们要让贡献最小
具体做法就是令树的边权都为1,先求一下树的直径 d1 ,把直径上的边权变为-1,然后再求一遍直径 d2
那么答案就是 2nd1d2+2
其中 d1=a,d2=b2c
由于存在负边权,我一开始是想用奇怪的方法来改进bfs,使其能求负权树的边权,调试多次未果后只能改成dfs,用类似树形dp的方法来求,具体做法就是dfs整棵树,对每一棵子树记录一个最大值 mx 和次大值 sx d2=max(d2,mx+sx) ,返回值为 mx
复杂度 O(n)
代码:

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#define M 100005
using namespace std;
int n,k,tot=1,cnt,ans;
int first[M],dis[M],up[M];
struct edge{
    int v,w,next;
}e[M<<1];
void add(int x,int y)
{
    e[++tot].v=y;e[tot].w=1;e[tot].next=first[x];first[x]=tot;
    e[++tot].v=x;e[tot].w=1;e[tot].next=first[y];first[y]=tot;
}
queue<int>q;
int bfs(int x)
{
    int t=0;
    memset(dis,0,sizeof(dis));
    for (dis[x]=0,q.push(x);!q.empty();q.pop())
    {
        x=q.front(); 
        for (int i=first[x];i;i=e[i].next)
            if (!dis[e[i].v])
            dis[e[i].v]=dis[x]+e[i].w,
            t=(!t||dis[t]<dis[e[i].v]?e[i].v:t),
            up[e[i].v]=i,
            q.push(e[i].v);
    }
    return t;
}
int dfs(int x,int fa)
{
    int now,sx=0,mx=0;
    for (int i=first[x];i;i=e[i].next)
        if (e[i].v!=fa)
        {
            now=dfs(e[i].v,x)+e[i].w;
            if (now>mx)
                sx=mx,mx=now;
            else if (sx<now&&now<=mx)
                sx=now;
            cnt=max(cnt,mx+sx);
        }
    return mx;
}
main()
{
    scanf("%d%d",&n,&k);
    int x,y,mx;
    for (int i=1;i<n;++i)
        scanf("%d%d",&x,&y),
        add(x,y);
    x=bfs(1),y=bfs(x);
    ans=n-1<<1;
    ans-=dis[y]-1;
    for (;y!=x;y=e[up[y]^1].v)
        e[up[y]].w=e[up[y]^1].w=-1;
    if (k==1) return printf("%d\n",ans),0;
    dfs(1,0);
    printf("%d\n",ans-cnt+1);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值