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;
}