hihocoder#1845 : 泥泞的道路(最短路+斜率优化)

时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
有n个城市,有m条连接两个城市的道路,每条道路有一个初始(第0天)的泥泞程度 z i z_i zi,每天泥泞程度都会增加1。

现在有Q组询问,每组询问问你第i天从1号城市到 j j j号城市所经过道路的泥泞程度总和最少为多少。

输入
第一行三个数, n,m,Q(n<=1000,m<=2000,Q<=600000)

接下来m行,每行三个数 x i , y i , z i x_i,y_i,z_i xi,yi,zi表示城市 x i x_i xi y i y_i yi之间有一条初始泥泞程度为 z i z_i zi的道路( z i z_i zi<=1000)

接下来Q行,每行两个数, j j j i i i,表示一组询问(0 <= i <= 1000000000)

输出
对于每组询问输出一个数表示答案

样例输入
3 3 2
1 2 1
2 3 1
1 3 10
3 0
3 100
样例输出
2
110

思路:因为路的泥泞度会发生变化,所以最短路跟边的数量也有关系,从样例即可看出。

d [ i ] [ j ] = d[i][j]= d[i][j]=从点1出发恰好经过 j j j条边到达点 i i i的最短路 ( 1 ≤ j ≤ n ) (1\le j\le n) (1jn)
这个可以利用最短路算法求出。

那么经过 x x x天后到达 i i i点的最短距离 a n s = m i n ( d [ i ] [ j ] + j ∗ x ) , 1 ≤ j ≤ m ans=min(d[i][j]+j*x),1\le j\le m ans=min(d[i][j]+jx),1jm

如果查询时,暴力枚举的话复杂度可能会达到 O ( m Q ) O(mQ) O(mQ)

可以发现 a n s = d [ i ] [ j ] + j ∗ x ans=d[i][j]+j*x ans=d[i][j]+jx这个式子可以利用斜率优化,使 a n s ans ans的取值在一个抛物线上。

然后查询就可以利用三分来求答案了。


对于评论的小伙伴我作了个图解释一下。
我们先考虑目的地固定的情况,假设所有询问的目的地均为点 n n n
我们可以先画出一个简单的坐标系,那么对于对于不同的天数 x x x,点 ( x , d [ n ] [ x ] ) (x,d[n][x]) (x,d[n][x])在二维坐标系中的分布图大致如下。并可以先对这些点求一个下半部分的凸包。

那么当第 k k k天时, a n s = m i n ( d [ n ] [ x ] + k ∗ x ) ( 1 < = x < = m ) ans=min(d[n][x]+k*x)(1<=x<=m) ans=min(d[n][x]+kx)(1<=x<=m)
b = d [ n ] [ x ] + k ∗ x = y + k ∗ x b=d[n][x]+k*x=y+k*x b=d[n][x]+kx=y+kx,那么 a n s = m i n ( a n s , b ) ans=min(ans,b) ans=min(ans,b)
上式转换过来就是 y = k ∗ x + b y=k*x+b y=kx+b y = d [ n ] [ x ] y=d[n][x] y=d[n][x] x x x都已知。
那么我们要求的就是对于经过点 ( x , d [ n ] [ x ] ) (x,d[n][x]) (x,d[n][x]),且斜率为 k k k的直线的 b b b值,而当 x = 0 x=0 x=0时,我们可以得到 b b b值。如下图。
在这里插入图片描述
由于斜率 k k k不变,而 x x x可以变化,我们可以平移直线 y = k ∗ x + b y=k*x+b y=kx+b,使其与点 ( x , d [ n ] [ x ] ) (x,d[n][x]) (x,d[n][x])相交。
那么显而易见,当直线越往下平移时, b b b值越小,那么我们更新得到的 a n s ans ans也越小。而这里我们可以用之前求得的凸包,根据斜率来三分得到最小的 b b b值。

代码里的 q q q数组存储的就是凸包里的点的 x x x坐标值。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e3+10;
const int MOD=1e9+7;
const double PI=acos(-1.0);
typedef long long ll;
struct lenka
{
    int now,step;
    ll cost;
    bool operator<(const lenka& A)const{return A.cost<cost;}
};
vector<pair<int,int> >e[MAX];
ll d[MAX][2*MAX];
int n,m;
void bfs()
{
    priority_queue<lenka>p;
    p.push({1,0,0});
    while(!p.empty())
    {
        lenka A=p.top();p.pop();
        if(A.cost!=d[A.now][A.step])continue;
        if(A.step==n)continue;
        for(int i=0;i<e[A.now].size();i++)
        {
            pair<int,int>nex=e[A.now][i];
            if(d[nex.first][A.step+1]==-1||d[nex.first][A.step+1]>A.cost+nex.second)
            {
                d[nex.first][A.step+1]=A.cost+nex.second;
                p.push({nex.first,A.step+1,A.cost+nex.second});
            }
        }
    }
}
int q[MAX][2*MAX];
int up[MAX];
int main()
{
    int Q;
    cin>>n>>m>>Q;
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        e[x].push_back({y,z});
        e[y].push_back({x,z});
    }
    memset(d,-1,sizeof d);
    d[1][0]=0;
    bfs();
    for(int i=2;i<=n;i++)
    {
        int R=0;
        for(int j=1;j<=m;j++)
        {
            if(d[i][j]==-1)continue;
            while(R>1&&(d[i][j]-d[i][q[i][R-1]])*1ll*(j-q[i][R-2])<(d[i][j]-d[i][q[i][R-2]])*1ll*(j-q[i][R-1]))R--;
            q[i][R]=j;
            R++;
        }
        up[i]=R-1;
    }
    while(Q--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if(x==1){puts("0");continue;}
        int l=0,r=up[x];
        while(r-l>1)
        {
            int mid=(l+r)/2;
            int mm=(r+mid)/2;
            if(d[x][q[x][mid]]+1ll*q[x][mid]*y>d[x][q[x][mm]]+1ll*q[x][mm]*y)l=mid;
            else r=mm;
        }
        printf("%lld\n",min(d[x][q[x][l]]+1ll*q[x][l]*y,d[x][q[x][r]]+1ll*q[x][r]*y));
    }
    return 0;
}

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值