bzoj3482,jzoj3238-超时空旅行hiperprostor【最短路,凸包,斜率优化】

正题

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3482


题目大意

一张有向图有正整数边权也有 x x x边权。其中 x x x可以取任何值(但是要注意所有的 x x x边必须长度相等),每次询问求 S S S T T T的可能最短路长度个数和它们的和。


解题思路

分层图,第 i i i层第 j j j个点表示 S S S i i i的最短路且经过了 j j j x x x的边方案,然后跑最短路。

现在我们定义 f i f_i fi表示 S S S E E E经过 i i i条边 x x x边的最短路,然后设 x x x边的长度为 x x x

那么经过 i i i条边为最短路的情况当且仅当 f i + i ∗ x f_i+i*x fi+ix最小。那么我们可以定义若干条一次函数 y = f i + i ∗ x y=f_i+i*x y=fi+ix,那么就是一条以 f i f_i fi为截距, i i i为斜率的线。那么我们考虑 y = f i + i ∗ x y=f_i+i*x y=fi+ix为最短路的可能情况。

维护一个 y = f i + i ∗ x y=f_i+i*x y=fi+ix的凸包,维护一个单调队列,让剩下的线的两两之间的交点的 x x x坐标递增即可。

然后就可以算出第一问,对于第二问用等差数列计算即可。


c o d e code code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<queue>
#define ll long long
#define p(a1,a2) ((a2)*n+a1)
using namespace std;
const ll N=510,M=10100,inf=1e18;
struct node{
	ll to,next,w;
}a[M*2];
struct line{
	double x,y;
}l[N];
ll n,m,ls[N*N],f[N*N],tot,Q,num,ans,next[N];
double ata[N];bool v[N*N];
queue<ll> q;
void addl(ll x,ll y,ll w)
{
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;
	a[tot].w=w;
}
ll read() {
	ll x=0,f=1; char c=getchar();
	if(c=='x') return -1;
	while(!isdigit(c)) {if(c=='x')f=-f;c=getchar();}
	while(isdigit(c)) x=(x<<1)+(x<<3)+c-48,c=getchar();
	return (!f)?-1:x;
}
void spfa(ll s)
{
	memset(v,0,sizeof(v));
	memset(f,127,sizeof(f));
	q.push(s);v[s]=1;f[s]=0;
	while(!q.empty())
	{
		ll x=q.front();q.pop();
		ll c=(x-1)/n;
		if(c==n) continue;
		for(ll i=ls[(x-1)%n+1];i;i=a[i].next)
		{
			ll y=p(a[i].to,c+(a[i].w==-1));
			if(f[x]+max(a[i].w,0ll)<f[y])
			{
				f[y]=f[x]+max(a[i].w,0ll);
				if(!v[y]){
					v[y]=1;
					q.push(y);
				}
			}
		}
		v[x]=0;
	}
	return;
}
double gtan(double x1,double y1,double x2,double y2)
{return (y2-y1)/(x1-x2);}
int main()
{
	n=read();m=read();
	for(ll i=1;i<=m;i++)
	{
		ll x,y,w;
		x=read();y=read();w=read();
		if(x==y) continue;
		addl(x,y,w);
	}
	Q=read();
	while(Q--)
	{
		ll s=read(),t=read();
		bool flag=0;
		spfa(p(s,0));
		for(ll i=0;i<=n;i++)
			if(f[p(t,i)]<inf){flag=1;break;}
		if(!flag) {printf("0 0\n");continue;}
		if(f[p(t,0)]>inf) {printf("inf\n");continue;}
		ll num=0,sum=0,cnt=0;
		for(ll i=n;i>=0;i--){
			if(f[p(t,i)]>inf) continue;
			while(cnt>=1&&gtan(l[cnt].x,l[cnt].y,i,f[p(t,i)])<=ata[cnt]) cnt--;
			l[++cnt]=(line){i,f[p(t,i)]};
			if(cnt>1) ata[cnt]=gtan(l[cnt-1].x,l[cnt-1].y,l[cnt].x,l[cnt].y);
		}
		for(ll i=1;i<cnt;i++){
			ll L=(int)ata[i]+1,R=(int)ata[i+1];
			if(L<=R) sum+=(ll)(L*l[i].x+l[i].y+R*l[i].x+l[i].y)*(R-L+1)/2;
		}
		num=(ll)ata[cnt];
		if(ata[cnt]!=num||cnt==1) num++,sum+=f[p(t,0)];
		printf("%lld %lld\n",num,sum);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值