【COCI 2013】hiperprostor

Description

在遥远的未来,行星之间的食品运输将依靠单向的贸易路线。每条路径直接连接两个行星,且其运输时间是已知的。

贸易商协会打算利用一项最近发现的新技术——超空间旅行,以增加一些新的航线。通过超空间旅行的航线也是单向的。由于该项技术仍处于试验阶段,超空间旅行的时间目前是未知的,但它不取决于行星之间的距离,所以每个超空间旅行的路线将花费等量的时间。

下图是三个相互联通的行星及其运输时间的例子。行星使用正整数标号,超空间旅行时间记为“x”(图片对应第二个输入样例):

运输时间以天计,并且始终是一个正整数。

贸易商协会希望对引进新航线的结果进行分析:对于某两个行星A和B,他们想知道对于任意的x,从A到B的最短路径的总运输时间的所有可能的值。例如,在上述情况中,从星球2到星球1的最短路径所需时间可以取值5(如果x≥5),4,3,2,或1天(如果x<5)

Solution

可以发现,最后的最短路一定是多条形如 a x + b ax+b ax+b 这样的式子。这跟一次函数的解析式很像,而最短路也就是求出这些直线每个时刻的最小值。画图发现求的是上凸壳。

根据思路,先 spfa \text{spfa} spfa 求出 d i s i , j dis_{i,j} disi,j,表示走了 j j j 条 x 边,到达点 i i i 的最短路。而 d i s i , j dis_{i,j} disi,j 本身就是直线的截距。

单调队列维护上凸壳。

计算答案时,将相邻两直线的交点求出,用等差数列计算即可。为了避免重复,可将截距减 1 后再计算交点。

Code

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 505
#define M 10005
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
int n,m,x,y,z,tot,q,cnt,dis[N][N],sta[N];
ll ans1,ans2;
bool flag,bj[N][N];
char ch;
struct node {int to,next,head,val,typ;} a[M];
struct mindis {int pos,num;};
queue<mindis> Q;
int read()
{
    int res=0,fh=1;char ch=getchar();
    while (ch<'0'||ch>'9'&&ch!='x') {if (ch=='-') fh=-1;ch=getchar();}
    if (ch=='x') return 0;
    while (ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch-'0'),ch=getchar();
    return res*fh;
}
void add(int x,int y,int z)
{
    a[++tot].to=y;a[tot].val=z;
    a[tot].next=a[x].head;a[x].head=tot;
}
void spfa(int st,int ed)
{
    while (!Q.empty()) Q.pop();
    memset(dis,inf,sizeof(dis));
    memset(bj,false,sizeof(bj));
    Q.push((mindis){st,0});dis[st][0]=0;bj[st][0]=true;
    while (!Q.empty())
    {
        int x=Q.front().pos,y=Q.front().num;Q.pop();
        if (y>=n) continue;
        bj[x][y]=true;
        for (int i=a[x].head;i;i=a[i].next)
        {
            if (dis[a[i].to][y+(a[i].val==0)]>dis[x][y]+a[i].val)
            {
                dis[a[i].to][y+(a[i].val==0)]=dis[x][y]+a[i].val;
                if (!bj[a[i].to][y+(a[i].val==0)]) Q.push((mindis){a[i].to,y+(a[i].val==0)}),bj[a[i].to][y+(a[i].val==0)]=true;
            }
        }
        bj[x][y]=false;
    }
}
double calc(int x,int y,int ed) {return 1.0*(dis[ed][x]-dis[ed][y])/(y-x);}
int main()
{
    freopen("T1.in","r",stdin);
    freopen("T1.out","w",stdout);
    n=read();m=read();
    for (int i=1;i<=m;++i)
    {
        x=read();y=read();z=read();
        add(x,y,z);
    }
    scanf("%d",&q);
    while (q--)
    {
        x=read();y=read();
        spfa(x,y);
        bool flag=false;
        for (int i=0;i<=n;++i)
            flag|=(dis[y][i]<inf);
        if (!flag) {printf("0 0\n");continue;}
        if (dis[y][0]>=inf) {printf("inf\n");continue;}
        sta[cnt=1]=0;
        for (int i=1;i<=n;++i)
            if (dis[y][i]<dis[y][sta[cnt]])
            {
                while (calc(sta[cnt],sta[cnt-1],y)<calc(i,sta[cnt],y)&&cnt>1) --cnt;
                sta[++cnt]=i;
            }
        ans1=1;ans2=dis[y][0];
        for (int i=cnt,lst=0;i>1;--i)
        {
            x=((dis[y][sta[i-1]]-dis[y][sta[i]]-1+0.0)/(sta[i]-sta[i-1]));
            ans2+=((ll)sta[i]*(x+lst+1)+(ll)2*dis[y][sta[i]])*(ll)(x-lst)/2;
            ans1=x+1;lst=x;
        }
        printf("%lld %lld\n",ans1,ans2);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值