[BZOJ]2282: [Sdoi2011]消防 树的直径+单调队列

Description

某个国家有n个城市,这n个城市中任意两个都连通且有唯一一条路径,每条连通两个城市的道路的长度为zi(zi<=1000)。
这个国家的人对火焰有超越宇宙的热情,所以这个国家最兴旺的行业是消防业。由于政府对国民的热情忍无可忍(大量的消防经费开销)可是却又无可奈何(总统竞选的国民支持率),所以只能想尽方法提高消防能力。
现在这个国家的经费足以在一条边长度和不超过s的路径(两端都是城市)上建立消防枢纽,为了尽量提高枢纽的利用率,要求其他所有城市到这条路径的距离的最大值最小。
你受命监管这个项目,你当然需要知道应该把枢纽建立在什么位置上。

题解:

好题啊!显然这条路径在树的直径上,然后维护一个单调队列,然后用两个指针,表示现在是那一段路径,单调队列用来维护路径上的点到非直径上的点的最长距离,而到直径上的点的距离是可以直接用前缀和相减算出来的。代码奇丑奇慢,建议不要抄。

代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int Maxn=300010;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
struct Edge{int y,d,next;}e[Maxn*2];
int last[Maxn],len=0;
void ins(int x,int y,int d)
{
    int t=++len;
    e[t].y=y;e[t].d=d;e[t].next=last[x];last[x]=t;
}
int n,s,fa[Maxn];
void dfs(int x,int f)
{
    fa[x]=f;
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(y!=f)dfs(y,x);
    }
}
int ans,p,st,ed,Next[Maxn],f[Maxn],ff[Maxn],q[Maxn];
bool vis[Maxn];
void work(int x,int t)
{
    vis[x]=true;
    if(t>ans){ans=t;p=x;}
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(!vis[y])work(y,t+e[i].d);
    }
}
map<int,map<int,int> >dis;
int main()
{
    n=read();s=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read(),d=read();
        ins(x,y,d);ins(y,x,d);
        dis[x][y]=dis[y][x]=d;
    }
    dfs(1,1);
    memset(vis,false,sizeof(vis));ans=0;work(1,0);st=p;
    memset(vis,false,sizeof(vis));ans=0;work(p,0);ed=p;
    memset(vis,false,sizeof(vis));
    int x=st,lca;
    vis[x]=true;
    while(x!=1)Next[x]=fa[x],vis[x=fa[x]]=true;
    x=ed;
    while(!vis[x])Next[fa[x]]=x,x=fa[x];
    lca=x;
    x=st;f[st]=0;
    while(x!=ed)
    {
        f[Next[x]]=f[x]+dis[x][Next[x]];
        x=Next[x];
    }
    memset(vis,false,sizeof(vis));
    x=st;vis[st]=true;
    while(Next[x]!=ed)vis[x=Next[x]]=true;
    vis[ed]=true;
    x=st;ans=0;work(st,0);ff[st]=ans;
    while(Next[x]!=ed)
    {
        x=Next[x];
        ans=0;work(x,0);ff[x]=ans;
    }
    ans=0;work(ed,0);ff[ed]=ans;
    int head=st,tail=st,tot=0,Ans=f[ed],l=1,r=0;
    while(1)
    {
        while(tail!=ed&&tot+dis[tail][Next[tail]]<=s)
        {
            while(l<=r&&ff[q[r]]<=ff[tail])r--;
            q[++r]=tail;
            tot+=dis[tail][Next[tail]],tail=Next[tail];
        }
        Ans=min(Ans,max(max(f[head]-f[st],f[ed]-f[tail]),ff[q[l]]));
        if(tail==ed)break;
        tot-=dis[head][Next[head]];head=Next[head];
        if(q[l]<=head)l++;
    }
    printf("%d",Ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值