POI2014 Freight

Freight

POI2014

题意

1.火车从一侧到另一侧需要S分钟。

2.每两列车发车时间至少需要间隔一分钟。

3.在每一个时刻,在铁路上的所有列车的行驶方向都必须相同。

4.按照时间表的顺序,N辆列车将从左侧到右侧,到达后又需要回到左侧。

5.问最迟回到左侧的列车回到左侧的时刻的最小是多少。

1.贪心一波结论
如果现在有车回去,那么必然是所有车一起回去
因为所有车一起回来可以让回来的耗时最少,并且可以让将要过来的车尽量集中(意会一下即可。。。)

2.预处理一波
由于两辆车发车至少要间隔一分钟
预处理A[i]=max(A[i-1]+1,A[i])
保证A[i]严格递增

3.动态规划一波
定义
dp[i]:前i辆车,完成来回,最后一辆车回到左侧的最早时间

4.状态转移
dp[i]=min{max(max(dp[j],A[j+1])+i-j-1,A[i])+S+S+i-j-1}

explain
上一次完成来回的是前j辆车,
A[j]车发车时间是max(dp[j],A[j+1])

A[i]车发车时间是max(dp[j],A[j+1])+i-j-1,A[i])

A[i]车到达时间 发车时间+S

全部回去还需要S+i-j-1的时间

5.一波神乎其神的优化(如果你想看O(n)解法请跳过此条)


dp[i]=min(dp[i],max(max(dp[j],A[j+1])+i-j-1,A[i])+S+S+i-j-1);

提出 max(max(dp[j],A[j+1])-j-1+i,A[i])+S+S+i-j-1
令 B[j]=max(dp[j],A[j+1])-j-1;

则,原式=max(B[j]+i,A[i])-j-1+2*S+i
       =max(B[j]-j+i,A[i]-j)+2*S+i-1

令 C[j]=B[j]-j=max(dp[j],A[j+1])-2*j-1

  则上式=max(C[j]+i,A[i]-j)+2*S+i-1
      
分类讨论:
1.C[j]+i<=A[i]-j 
即C[j]+j<=A[i]-i    dp[i]=min{A[i]-j+2*S+i-1}=min{A[i]+2*S+i-1 -j}
目标:找到最大的j

2.C[j]+i>=A[i]-j
即C[j]+j>=A[i]-i    dp[i]=min{C[j]+i+2*S+i-1}=min{C[j] +2*i+2*S-1}
目标:找到最小的C[j]

单点更新,区间查询
查询[~,A[i]-i]中的最大j
查询[A[i]-i,~]中的最小C[j]

然后 nlogn 悲催的T掉了。。(必然是OJ太慢了,我这BIT的常数还不够优秀吗?!)
于是我很不要脸的加了个 Ofast 卡了过去- -

具体代码

#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
const int M=1000005;
int n,A[M],B[M],len,S;
long long dp[M],C[M];
int mx[M];
long long mi[M];
void add1(int x,int a) {
    while(x<=len) {
        mx[x]=max(mx[x],a);
        x+=-x&x;
    }
}
int query1(int x) {
    int res=-1e9;
    while(x) {
        res=max(res,mx[x]);
        x-=-x&x;
    }
    return res;
}
void add2(int x,long long a) {
    while(x) {
        mi[x]=min(mi[x],a);
        x-=-x&x;
    }
}
long long query2(int x) {
    long long res=1e18;
    while(x<=len) {
        res=min(res,mi[x]);
        x+=-x&x;
    }
    return res;
}
void insert(int x) {
    long long c=C[x]+x;
    int p=lower_bound(B+1,B+1+len,c)-B;
    add1(p,x);
    p=upper_bound(B+1,B+1+len,c)-B-1;
    add2(p,C[x]);
}
int main() {
    scanf("%d %d",&n,&S);
    len=0;
    for(int i=1; i<=n; i++) {
        scanf("%d",&A[i]);
        if(i>1)A[i]=max(A[i-1]+1,A[i]);
        B[++len]=A[i]-i;
    }
    B[++len]=0;
    sort(B+1,B+1+len);
    len=unique(B+1,B+1+len)-B-1;
    memset(mx,-63,sizeof(mx));
    memset(mi,63,sizeof(mi));
    dp[0]=0;
    C[0]=max(dp[0],1ll*A[1])-1;
    insert(0);
    for(int i=1; i<=n; i++) {
        int p=lower_bound(B+1,B+1+len,A[i]-i)-B;
        dp[i]=1e18;
        dp[i]=min(dp[i],1ll*A[i]+2*S+i-1-query1(p));
        dp[i]=min(dp[i],query2(p)+2*i+2*S-1);
        C[i]=max(dp[i],1ll*A[i+1])-2*i-1;
        insert(i);
    }
    printf("%lld\n",dp[n]);
    return 0;
}

6.那么无视第5条,我们看看正解
转移式:
dp[i]=min{max(max(dp[j],A[j+1])+i-j-1,A[i])+S+S+i-j-1}

事实上直接考虑
dp[i]=min{max(dp[j]+i-j-1,A[i])+S+S+i-j-1}

因为 A数组单调递增
所以 A[i]-A[j+1]>=i-j-1
于是 不用考虑A[j+1]的问题

分类讨论

1.如果dp[j]+i-j-1<A[i]
取最大的j,可以减少这次返程的时间

2.如果dp[j]+i-j-1>=A[i]
dp[i]=dp[j]+S*2+2 *(i-j-1)
取最小的j

具体代码

#include<bits/stdc++.h>
using namespace std;
const int M=1000005;
int n,A[M],S;
long long dp[M];
int q[M];
int main() {
	scanf("%d %d",&n,&S);
	for(int i=1; i<=n; i++) {
		scanf("%d",&A[i]);
		if(i>1)A[i]=max(A[i-1]+1,A[i]);
	}
	int pos=0;
	for(int i=1; i<=n; i++) {
		while(pos<i&&dp[pos]-pos-1+i<A[i])pos++;
		dp[i]=1ll*A[i]+S*2+i-pos;
		if(pos<i)dp[i]=min(dp[i],dp[pos]+S*2+(i-pos-1)*2);
	}
	printf("%lld\n",dp[n]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值