COCI2013 HIPERPROSTOR

题目大意:

给定一张有向图,n<=500,m<=10000,其中有一些特殊边,长度相同是一个正整数但是未知,设为X。给出Q<=10个询问S,T,输出当x取遍所有正整数的时候,ST的最短路的所有可能值的个数以及总和。

分析:每一条ST的路径,设路径上x的条数有k个,其余的和为b,那么路径总长就是kx+b。对于相同的k我们只关心最小的b。于是我们可以设状态f[i][j],表示从S走到j,经过ix的边的最小b值。最短路的边数最多只有n-1条,所以k的范围是[0,n)。状态数是On^2)。F[][]数组我们可以通过spfa求出(事实证明spfadijk快很多)。

接下来我们要处理的是n条直线y=kx+b。若无解,则肯定是n条直线的b值都无穷大;若有无穷解,则k=0的直线b值无穷大。剩下的情况一定有有限解。我们可以按k值从大到小插入平面,On)维护出一个凸壳,维护凸壳的时候注意存储下凸壳上相邻两条直线的整点坐标,那么相邻两条直线的对总个数的贡献就是交点横坐标差,对总和的贡献其实就是一个等差数列的和,都可以很容易算出。

综上,一次询问的复杂度就是spfa的复杂度+On)。

代码:

/*
ID:huangta3
PROG:
LANG:C++
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int maxn=503,maxm=10003,inf=1000000000;
int n,m,Link[maxn],t[maxm],pre[maxm],v[maxm],S,T,f[maxn][maxn],top;
bool vis[maxn][maxn];
struct Tline
{
    int k,x;
    inline Tline(){}
    inline Tline(int _k,int _x){k=_k,x=_x;}
}s[maxn];
inline int get()
{
    int f=0,v=0;char ch;
    while(!isdigit(ch=getchar()))if(ch=='-')break;
    if(ch=='-')f=1;else v=ch-48;
    while(isdigit(ch=getchar()))v=v*10+ch-48;
    if(f==1)return -v;else return v;
}
int q[maxn*maxn][2];
inline void spfa()
{
    memset(f,120,sizeof(f));
    memset(vis,0,sizeof(vis));
    int front=0,rear=1;
    f[0][S]=0;q[front][0]=0,q[front][1]=S;vis[0][S]=1;
    while(front!=rear)
    {
        int K=q[front][0],T=q[front][1],V=f[K][T];
        for(int i=Link[T];i;i=pre[i])
            if(!v[i])
            {
                if(K+1<n&&f[K+1][t[i]]>V)
                {
                    f[K+1][t[i]]=V;
                    if(!vis[K+1][t[i]])vis[K+1][t[i]]=1,q[rear][0]=K+1,q[rear++][1]=t[i];
                }
            }else
            {
                if(f[K][t[i]]>V+v[i])
                {
                    f[K][t[i]]=V+v[i];
                    if(!vis[K][t[i]])vis[K][t[i]]=1,q[rear][0]=K,q[rear++][1]=t[i];
                }
            }
        front++,vis[K][T]=0;
    }
}
 
inline bool spj()
{
    int x=inf;
    for(int i=0;i<n;i++)x=min(x,f[i][T]);
    if(x>=inf)return puts("0 0")|1;
    else if(f[0][T]>=inf)return puts("inf")|1;
    return 0;
}
inline int calc(int k,int x){return k*x+f[k][T];}
int main()
{
    n=get();m=get();
    for(int i=1;i<=m;i++)
    {
        int x=get(),y=get(),z=0;
        char ch=getchar();
        if(ch!='x')
        {
            z=ch-'0';
            while(isdigit(ch=getchar()))z=z*10+ch-'0';
        }
        pre[i]=Link[x]; Link[x]=i; t[i]=y; v[i]=z;
    }
    int Q=get();
    while(Q--)
    {
        S=get(),T=get();
        spfa();
        if(spj())continue;
        top=0;
        for(int i=n-1;i>=0;i--)
        {
            if(f[i][T]>=inf)continue;
            while(top)
            {
                int x=s[top].x,prev=calc(s[top].k,x),cur=calc(i,x);
                if(cur<=prev)top--;else break;
            }
            if(top)
            {
                double k1=s[top].k,k2=i,b1=f[s[top].k][T],b2=f[i][T];
                int x=(int)ceil((b2-b1)/(k1-k2));
                s[++top]=Tline(i,x);
            }else s[++top]=Tline(i,0);
        }
        int tot=1;LL sum=f[0][T];
        for(int i=1;i<top;i++)
        {
            int st=s[i].x,ed=s[i+1].x-1;
            if(st==0)st++;
            tot+=ed-st+1;
            sum+=LL(ed-st+1)*(calc(s[i].k,st)+calc(s[i].k,ed))/2;
        }
        cout<<tot<<" "<<sum<<endl;
    }
    return 0;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值