【USACO 2009 JAN GOLD】安全路径

时间限制 : 20000 MS 空间限制 : 65536 KB

问题描述

Gremlins最近在农场上泛滥,它们经常会阻止牛们从农庄(牛棚_1)走到别的牛棚(牛_i的目的地是牛棚_i)。每一个gremlin只认识牛_i并且知道牛_i一般走到牛棚_i的最短路经。所以它们在牛_i到牛棚_i之前的最后一条牛路上等牛_i,当然,牛不愿意遇到Gremlins,所以准备找一条稍微不同的路经从牛棚_1走到牛棚_i,所以,请你为每一头牛_i找出避免gremlin_i的最短路经的长度。
和以往一样,农场上的M (2 <= M <= 200,000)条双向牛路编号为1..M并且能让所有牛到达它们的目的地,N(3 <= N <=100,000)个编号为1..N的牛棚。牛路i连接牛棚a_i (1 <= a_i <= N)和b_i (1 <= b_i <=N)并且需要时间t_i (1 <=t_i <=1,000)通过。没有两条牛路连接同样的牛棚,所有牛路满足a_i!=b_i。在所有数据中,牛_i使用的牛棚_1到牛棚_i的最短路经是唯一的。
以下是一个牛棚,牛路和时间的例子:这里写图片描述

输入格式

第一行:两个空格分开的数N和M;
第2..M+1行:三个空格分开的数a_i, b_i,和t_i

输出格

第1..N-1行:第i行包含一个数,从牛棚_1到牛棚_i+1并且避免从牛棚1到牛棚i+1最短路经上最后一条牛路的最少的时间。如果这样的路经不存在,输出-1。

样例输入

4 5
1 2 2
1 3 2
3 4 4
3 2 1
2 4 3

样例输出

3
3
6

提示

20%的数据满足,N<=200;
50%的数据满足,N<=3000
100%的数据满足,N<=100,000

题解

本题中,奶牛们的原路径由从1号点出发到每个点的最短路组成,这提示我们可能需要建一棵最短路树,并在上面做点文章。显然,按照题目的要求,奶牛会经过不在最短路树上的另一条边绕过去。设我们要到的点为 k k ,有一条不在最短路上的边为(x,y) t=LCA(x,y) t = L C A ( x , y ) k k t y y 的路径上。那么奶牛绕行的距离可表示为dis[x]+len[x][y]+dis[y]dis[k],可以发现只要 dis[x]+len[x][y]+dis[y] d i s [ x ] + l e n [ x ] [ y ] + d i s [ y ] 最小,所求答案就可最小。因此我们枚举不在最短路树的每一条边,更新两端点与 LCA L C A 间的点即可(注意不能更新 LCA L C A )。显然可以树链剖分+线段树维护最小值。(图片引自何老板PPT)
这里写图片描述
顺便复习了一拨dijkstra+heap,才发现自己一直写的是SPFA式的假dijkstra……QAQ

代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef pair<int,int> pii;
const int maxn=1e5+5;
const int inf=1e9;
int a,b,c,n,m,cnt=1,dis[maxn],fro[maxn],Last[maxn],St[maxn<<2],End[maxn<<2],Len[maxn<<2],Next[maxn<<2];
int vt,fa[maxn],dep[maxn],dfn[maxn],siz[maxn],son[maxn],top[maxn];
int lazy[maxn<<2],tree[maxn<<2];
bool vis[maxn],mark[maxn<<2];
priority_queue<pii,vector<pii>,greater<pii> > q;
void Addedge(int x,int y,int z)
{
    End[++cnt]=y,St[cnt]=x,Len[cnt]=z;
    Next[cnt]=Last[x],Last[x]=cnt;
}
void dijkstra(int x)
{
    for(int i=1;i<=n;i++) dis[i]=inf;
    dis[1]=0,q.push(make_pair(0,1));
    while(!q.empty())
    {
        int x=q.top().second;q.pop();
        if(vis[x]) continue;vis[x]=true;
        for(int i=Last[x];i;i=Next[i])
        {
            int y=End[i];
            if(dis[x]+Len[i]<dis[y])
            {
                dis[y]=dis[x]+Len[i],q.push(make_pair(dis[y],y));
                mark[fro[y]]=false,fro[y]=i,mark[i]=true;
            }
        }
    }
}
void Get_siz(int x)
{
    dep[x]=dep[fa[x]]+1,siz[x]=1;
    for(int i=Last[x];i;i=Next[i])
        if(mark[i])
        {
            int y=End[i];
            if(y==fa[x]) continue;
            fa[y]=x,Get_siz(y),siz[x]+=siz[y];
            if(siz[y]>siz[son[x]]) son[x]=y;
        }
}
void Get_dfn(int x,int anc)
{
    if(!x) return;
    dfn[x]=++vt,top[x]=anc;
    Get_dfn(son[x],anc);
    for(int i=Last[x];i;i=Next[i])
        if(mark[i])
        {
            int y=End[i];
            if(y==fa[x]||y==son[x]) continue;
            Get_dfn(y,y);
        }
}
namespace segment
{
    void putdown(int p)
    {
        tree[p<<1]=min(tree[p<<1],lazy[p]);
        tree[p<<1|1]=min(tree[p<<1|1],lazy[p]);
        lazy[p<<1]=min(lazy[p<<1],lazy[p]);
        lazy[p<<1|1]=min(lazy[p<<1|1],lazy[p]);
        lazy[p]=inf;
    }
    void build(int p,int l,int r)
    {
        if(l<r)
        {
            int mid=(l+r)>>1;
            build(p<<1,l,mid),build(p<<1|1,mid+1,r);
            tree[p]=lazy[p]=inf;
        }
        else tree[p]=lazy[p]=inf;
    }
    void modify(int p,int l,int r,int x,int y,int k)
    {
        if(x<=l&&r<=y)
        {
            lazy[p]=min(lazy[p],k),tree[p]=min(tree[p],k);
            return;
        }
        if(lazy[p]<inf) putdown(p);
        int mid=(l+r)>>1;
        if(x<=mid&&y>=l) modify(p<<1,l,mid,x,y,k);
        if(y>mid&&x<=r) modify(p<<1|1,mid+1,r,x,y,k);
    }
    int query(int p,int l,int r,int x)
    {
        if(l==r) return tree[p];
        if(lazy[p]<inf) putdown(p);
        int mid=(l+r)>>1;
        if(x<=mid) return query(p<<1,l,mid,x);
        return query(p<<1|1,mid+1,r,x);
    }
}
int Get_lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]>dep[top[y]]) swap(x,y);
        y=fa[top[y]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    return x;
}
void change(int x,int y,int z)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]>dep[top[y]]) swap(x,y);
        segment::modify(1,1,n,dfn[top[y]],dfn[y],z);
        y=fa[top[y]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    segment::modify(1,1,n,dfn[x]+1,dfn[y],z);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        Addedge(a,b,c),Addedge(b,a,c);
    }
    dijkstra(1),Get_siz(1),Get_dfn(1,1);
    segment::build(1,1,n);
    for(int i=2;i<=cnt;i++)
        if(!mark[i])
        {
            int lca=Get_lca(St[i],End[i]);
            change(End[i],lca,dis[St[i]]+dis[End[i]]+Len[i]);
        }
    for(int i=2;i<=n;i++)
    {
        int temp=segment::query(1,1,n,dfn[i]);
        if(temp<inf) printf("%d\n",temp-dis[i]);
        else puts("-1");
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一道经典的单调栈问题。题目描述如下: 有 $n$ 个湖,第 $i$ 个湖有一个高度 $h_i$。现在要在这些湖之间挖一些沟渠,使得相邻的湖之间的高度差不超过 $d$。请问最少需要挖多少个沟渠。 这是一道单调栈的典型应用题。我们可以从左到右遍历湖的高度,同时使用一个单调栈来维护之前所有湖的高度。具体来说,我们维护一个单调递增的栈,栈中存储的是湖的下标。假设当前遍历到第 $i$ 个湖,我们需要在之前的湖中找到一个高度最接近 $h_i$ 且高度不超过 $h_i-d$ 的湖,然后从这个湖到第 $i$ 个湖之间挖一条沟渠。具体的实现可以参考下面的代码: ```c++ #include <cstdio> #include <stack> using namespace std; const int N = 100010; int n, d; int h[N]; stack<int> stk; int main() { scanf("%d%d", &n, &d); for (int i = 1; i <= n; i++) scanf("%d", &h[i]); int ans = 0; for (int i = 1; i <= n; i++) { while (!stk.empty() && h[stk.top()] <= h[i] - d) stk.pop(); if (!stk.empty()) ans++; stk.push(i); } printf("%d\n", ans); return 0; } ``` 这里的关键在于,当我们遍历到第 $i$ 个湖时,所有比 $h_i-d$ 小的湖都可以被舍弃,因为它们不可能成为第 $i$ 个湖的前驱。因此,我们可以不断地从栈顶弹出比 $h_i-d$ 小的湖,直到栈顶的湖高度大于 $h_i-d$,然后将 $i$ 入栈。这样,栈中存储的就是当前 $h_i$ 左边所有高度不超过 $h_i-d$ 的湖,栈顶元素就是最靠近 $h_i$ 且高度不超过 $h_i-d$ 的湖。如果栈不为空,说明找到了一个前驱湖,答案加一。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值