可以用差分约束,也可以用线段树优化的DP做。
大于或等于某个价值的东西可以经过一段时间换取价值比它的大的东西,问最少经过多少时间可以换到价值大于M的东西?
因为关系最多有10^5,东西最多就会有2*10^5个,最关键的是价钱大小,所以将所有的东西按照价值从小到大排序,便宜的i(对应的下标)经过一段时间可以换贵一点j的话,就连一条i到j,权值为w的边。因为贵的东西可以当作便宜的东西使,所以还要从贵东西向便宜的东西连边,如果每个关系都连l边的话,那么就太多了,但是如果每次将每一个价钱高一点向比他略小的一个的东西连一条权值为0的边,那么通过传递性肯定所有的大小关系就能表示了。。。不过注意原始可能没有价值为M的东西,所以需要加一个价值为M的东西。。。
#include<stdio.h>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=210000;
int q[maxn],S,T,n,nn,a[maxn];
bool inq[maxn];
long long dist[maxn];
long long maxint=0x7ffffffffffff;//权值要非常大。至少10^14次方
struct edge
{
int u,v,w;
}e[maxn],p0;
vector<edge>ee[maxn];
int BiSearch(int t)//二分查找钱对应的下标
{
int l=0,r=nn-1,mid;
while(l<=r)
{
mid=(l+r)/2;
if(a[mid]==t) return mid;
else if(a[mid]>t) r=mid-1;
else l=mid+1;
}
return mid;
}
void SPFA()//SPFA找最短路
{
int head=0,tail=0,i,j,k;
for(i=0;i<nn;i++)
{
inq[i]=false;
dist[i]=maxint;
}
dist[S]=0;
q[tail++]=S;
inq[S]=true;
while(head!=tail)
{
j=q[head];
inq[j]=false;
head=(head+1)%maxn;
for(i=0;i<ee[j].size();i++)
{
p0=ee[j][i];
k=p0.v;
if(dist[k]>dist[j]+p0.w)
{
dist[k]=dist[j]+p0.w;
if(!inq[k])
{
inq[k]=true;
q[tail]=k;
tail=(tail+1)%maxn;
}
}
}
}
}
int main()
{
int i,j,k,t,test=1,m;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
{
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
a[i*2]=e[i].u;//将状态保存
a[i*2+1]=e[i].v;
}
a[2*n]=m;//加入m这个点,因为可以原来没有这个点,但有比它大的点
sort(a,a+2*n+1);//排序
printf("Case #%d: ",test++);
for(i=1,j=1;j<=2*n;j++)//去重
if(a[j]!=a[j-1])
a[i++]=a[j];
nn=i;
ee[0].clear();
for(i=1;i<nn;i++)
{
ee[i].clear();
p0.u=i,p0.v=i-1,p0.w=0;//大的可以直接当小的用,所以每次加条权值为0的边
ee[i].push_back(p0);
}
S=0;
T=BiSearch(m);
if(a[0]!=1)
{
printf("-1\n");
continue;
}
for(i=0;i<n;i++)
{
j=BiSearch(e[i].u);//二分查找下标
k=BiSearch(e[i].v);
p0.u=k,p0.v=j,p0.w=e[i].w;
ee[k].push_back(p0);//k可以通过时间e[i].w换为j
}
SPFA();
if(dist[T]==maxint)
printf("-1\n");
else printf("%lld\n",dist[T]);
}
return 0;
}
用线段树压缩的DP:首先将关系(i,j,k)(价值i经过时间k变为价值j),按j从小到大排序,并且将每个价值离散化+排序+去重,然后根据价值下标建立线段树。然后对于每个(i,j,k),先扫描[ii,jj)(ii,jj分别为离散化后的价值数组对应i,j)找出在这段范围内的最少时间,加上k就表示j时刻的最少时间,将其插入线段树。最后只要查找所有比m大的最少时间即可。
#include<stdio.h>
#include<algorithm>
#include<iostream>
using namespace std;
struct point
{
int u,v,t;
}e[210000];
struct Tree
{
int l,r;
long long w;
}T[810000];
int num,a[210000];
bool cmp(point p1,point p2)
{
return p1.u<p2.u;//按能换到的价值从小到大排序
}
int BiSearch(int now)//在离散化后的价值数组中查找now,没有返回第一个比其大的
{
int l=0,r=num-1,mid;
while(l<=r)
{
mid=(l+r)/2;
if(a[mid]==now) return mid;
else if(a[mid]>now) r=mid-1;
else l=mid+1;
}
return l;
}
void build(int l,int r,int step)//[l,r]建立线段树
{
T[step].l=l,T[step].r=r,T[step].w=-1;
if(l>=r) return;
build(l,(l+r)/2,2*step);
build((l+r)/2+1,r,2*step+1);
}
long long Find(int l,int r,int step)//查找[l,r]间的最少时间
{
if(l>r) return -1;
if(T[step].l>=l&&T[step].r<=r) return T[step].w;
if(l>=T[2*step+1].l) return Find(l,r,2*step+1);
if(r<=T[2*step].r) return Find(l,r,2*step);
long long ii,jj;
ii=Find(l,T[2*step].r,2*step);
jj=Find(T[2*step+1].l,r,2*step+1);
if(ii==-1) return jj;
if(jj==-1) return ii;
if(ii<jj) jj=ii;
return jj;
}
void Insert(int index,long long now,int step)//在index下插入now
{
if(T[step].w==-1||T[step].w>now)
T[step].w=now;
if(T[step].r<=T[step].l) return;
if(index<=T[2*step].r)
Insert(index,now,2*step);
else Insert(index,now,2*step+1);
}
int main()
{
int t,test=1,n,m,i,j,ii,jj;
long long kk;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
{
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].t);
a[2*i]=e[i].u;
a[2*i+1]=e[i].v;
}
printf("Case #%d: ",test++);
sort(e,e+n,cmp);
sort(a,a+2*n);
for(num=1,i=1;i<2*n;i++)
if(a[i]!=a[i-1])
a[num++]=a[i];
if(a[0]!=1||a[num-1]<m)
{
printf("-1\n");
continue;
}
build(0,num-1,1);
Insert(0,0,1);
for(i=0;i<n;i++)
{
ii=BiSearch(e[i].v);
jj=BiSearch(e[i].u);
if(jj<=ii) continue;
kk=Find(ii,jj-1,1);
if(kk!=-1)
Insert(jj,kk+e[i].t,1);
}
printf("%lld\n",Find(BiSearch(m),num-1,1));
}
return 0;
}