[BZOJ3242][Noi2013]快餐店(树形dp+线段树)

245 篇文章 0 订阅
70 篇文章 0 订阅

题目描述

传送门

题解

如果题目给的是一棵树,那么答案显然应该是树的直径/2。
不过题目给的是一个环套树,那么如果我们暴力地删去环上的一条边然后求新生成的树的最长链,然后取min的话,当然是正确的,不过时间复杂度是 O(n2) 的难以承受。
那么我们可以这样考虑:因为要使最长链最短,那么我们的任务就是求出一种树的形态,使这种情况下的最长链在所有情况中最短。

首先暴力找环是必须的,时间复杂度 O(n)
然后分两种情况考虑:
①如果最长链不经过环,即最长链在外向树上,那么枚举环上的每一个点,然后在每一棵外向树上进行dp,就能求出外向树上的最长链。时间复杂度 O(n)
②如果最长链经过环,那么很显然它不会经过环上的某一条边(类似删边的思想)。假设s[i]表示以i为根的外向树从树根到叶节点的最长距离,sum[i]表示距离的前缀和(当前删掉的边不管,其余的变成一个序列)的话,那么最长链应该为 max{sum[i]sum[j]+s[i]+s[j]}=max{(sum[i]+s[i])(sum[j]+s[j])}=max{sum[i]+s[i]}min{sum[j]+s[j]} 。可以看出可以用两棵线段树来维护,不过因为i不能与j相等,所以还需要维护次大值和次小值。在枚举的过程中修改一下线段树中的值就好了。时间复杂度 O(nlogn)
值得注意的是第一种情况应该将所有的最长链取max,即无论如何删边答案都不会小于外向树上的最长链。但是第二种情况应该取min,以为要通过删边得到一组最优解。最后再将两种情况的答案取max。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<ctime>
using namespace std;
#define N 100005
#define LL long long

int n,x,y,cnt,last;
LL z,Max1,Max2,ans1,ans2,ans,t,lastsum;
int tot,point[N],nxt[N*2],v[N*2]; LL c[N*2];
int father[N],o[N];
LL len[N],length[N],f[N],g[N],s[N],sum[N],delta[N*4];
bool vis[N],flag;
struct hp{LL val;int num;}maxn[N*4],_maxn[N*4],minn[N*4],_minn[N*4],a[5];

inline int read()
{
    char ch=getchar(); int x=0;
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline void addedge(int x,int y,LL z)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;
}
inline void Circle(int x,int pt)
{
    while (x!=pt)
    {
        o[++cnt]=x;
        x=father[x];
    }
    o[++cnt]=pt;
}
inline void dfs(int x,int fa)
{
    vis[x]=true; father[x]=fa;
    for (int i=point[x];i&&(!flag);i=nxt[i])
        if (v[i]!=fa)
        {
            len[v[i]]=c[i];
            if (vis[v[i]])
            {
                Circle(x,v[i]);
                flag=true;
                break;
            }
            dfs(v[i],x);
        }
}
inline void treedp(int x,int fa)
{
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa&&!vis[v[i]])
        {
            treedp(v[i],x);
            if (f[v[i]]+c[i]>f[x])
            {
                g[x]=f[x];
                f[x]=f[v[i]]+c[i];
            }
            else g[x]=max(g[x],f[v[i]]+c[i]);
        }
    Max1=max(Max1,f[x]+g[x]);
    Max2=max(Max2,f[x]);
}
inline int cmpmax(hp a,hp b)
{
    return a.val>b.val;
}
inline int cmpmin(hp a,hp b)
{
    return a.val<b.val;
}
inline void update(int now)
{
    a[1].val=maxn[now<<1].val,a[1].num=maxn[now<<1].num;
    a[2].val=maxn[now<<1|1].val,a[2].num=maxn[now<<1|1].num;
    a[3].val=_maxn[now<<1].val,a[3].num=_maxn[now<<1].num;
    a[4].val=_maxn[now<<1|1].val,a[4].num=_maxn[now<<1|1].num;
    sort(a+1,a+5,cmpmax);

    maxn[now].val=a[1].val,maxn[now].num=a[1].num;
    for (int i=2;i<=4;++i)
        if (a[i].num!=maxn[now].num)
        {
            _maxn[now].val=a[i].val,_maxn[now].num=a[i].num;
            break;
        }

    a[1].val=minn[now<<1].val,a[1].num=minn[now<<1].num;
    a[2].val=minn[now<<1|1].val,a[2].num=minn[now<<1|1].num;
    a[3].val=_minn[now<<1].val,a[3].num=_minn[now<<1].num;
    a[4].val=_minn[now<<1|1].val,a[4].num=_minn[now<<1|1].num;
    sort(a+1,a+5,cmpmin);

    minn[now].val=a[1].val,minn[now].num=a[1].num;
    for (int i=2;i<=4;++i)
        if (a[i].num!=minn[now].num)
        {
            _minn[now].val=a[i].val,_minn[now].num=a[i].num;
            break;
        }
}
inline void build(int now,int l,int r)
{
    int mid=(l+r)>>1;
    if (l==r)
    {
        maxn[now].val=_maxn[now].val=sum[l]+s[l];
        minn[now].val=_minn[now].val=sum[l]-s[l];
        maxn[now].num=_maxn[now].num=minn[now].num=_minn[now].num=l;
        return;
    }
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    update(now);
}
inline void pushdown(int now,int l,int r,int mid)
{
    if (delta[now])
    {
        maxn[now<<1].val+=delta[now]; _maxn[now<<1].val+=delta[now];
        minn[now<<1].val+=delta[now]; _minn[now<<1].val+=delta[now];
        maxn[now<<1|1].val+=delta[now]; _maxn[now<<1|1].val+=delta[now];
        minn[now<<1|1].val+=delta[now]; _minn[now<<1|1].val+=delta[now];
        delta[now<<1]+=delta[now]; delta[now<<1|1]+=delta[now];
        delta[now]=0;
    }
}
inline void interval_change(int now,int l,int r,int lrange,int rrange,LL v)
{
    int mid=(l+r)>>1;
    if (lrange<=l&&r<=rrange)
    {
        maxn[now].val+=v; _maxn[now].val+=v;
        minn[now].val+=v; _minn[now].val+=v;
        delta[now]+=v;
        return;
    }
    pushdown(now,l,r,mid);
    if (lrange<=mid) interval_change(now<<1,l,mid,lrange,rrange,v);
    if (mid+1<=rrange) interval_change(now<<1|1,mid+1,r,lrange,rrange,v);
    update(now);
}
inline LL query()
{
    if (maxn[1].num!=minn[1].num) return maxn[1].val-minn[1].val;
    else return max(maxn[1].val-_minn[1].val,_maxn[1].val-minn[1].val);
}

int main()
{
    n=read();
    for (int i=1;i<=n;++i)
    {
        x=read(); y=read(); z=read();
        addedge(x,y,(LL)z);
    }
    dfs(1,0);

    for (int i=1;i<=cnt;++i) length[i]=len[o[i]];
    memset(vis,0,sizeof(vis));
    for (int i=1;i<=cnt;++i) vis[o[i]]=true;
    for (int i=1;i<=cnt;++i)
    {
        Max1=Max2=0;
        treedp(o[i],0);
        ans1=max(ans1,Max1);
        s[i]=Max2;
    }
    for (int i=1;i<cnt;++i)
        sum[i+1]=sum[i]+length[i];

    build(1,1,cnt);
    t=query();
    ans2=t;
    lastsum=sum[cnt];
    last=cnt;
    for (int i=1;i<cnt;++i)
    {
        interval_change(1,1,cnt,1,cnt,-length[i]);
        interval_change(1,1,cnt,i,i,lastsum+length[last]);
        t=query();
        ans2=min(ans2,t);
        lastsum=lastsum-length[i]+length[last];
        last=i;
    }
    ans=max(ans1,ans2);
    double loc=(double)ans/2;
    printf("%0.1lf\n",loc);
}


总结

①memset非常慢,时间应该是 O(n) 的,能不用就不用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值