2 条题解
-
1
考虑区间DP,你发现走的时候,按照顺序从左往右依次走肯定不优,因为打卡地点有开门时间,所以正确做法是在打卡地之间反复地跑。
先按照横坐标排序,然后你设 一开始 [i,j] 全部没打卡,且除此之外全打了卡,然后现在又到了 ,最小时间是多少。 类似地, 表示在 的最小时间。
然后转移的时候 可以从 和 转移过来, 可以从 和 转移过来。也就是从外边第一个往里边转移。
为什么这样是对的呢,因为如果除了 [i,j] 没转移,那么他可以从 ... 和 ... 走过来,然后其实就相当于从 , 走过来,因为如果从别的地方过来,一定会经过他们。
你枚举 和 的时候 从小往大, 从大往小,可以保证从大区间推到小区间,最后的答案就是
#include<bits/stdc++.h> #define int long long using namespace std; int n,l,c; struct node { int x,t; } a[1005]; int dp[1005][1005][2]; bool ccf(node A,node B) { return A.x<B.x; } void check(int mid) { memset(dp,0x3f,sizeof dp); dp[1][n][0]=max(a[1].t,a[1].x),dp[1][n][1]=max(a[n].t,a[n].x); for(int i=1; i<=n; ++i) { for(int j=n; j>=i; --j) { dp[i][j][0]=min(dp[i][j][0],min( max(dp[i-1][j][0]+a[i].x-a[i-1].x,a[i].t), max(dp[i][j+1][1]+a[j+1].x-a[i].x,a[i].t ))); dp[i][j][1]=min(dp[i][j][1],min( max(dp[i-1][j][0]+a[j].x-a[i-1].x,a[j].t), max(dp[i][j+1][1]+a[j+1].x-a[j].x,a[j].t ))); } } } signed main() { cin>>n>>l>>c; for(int i=1; i<=n; ++i) { cin>>a[i].x>>a[i].t; } sort(a+1,a+1+n,ccf); check(0); int ans=0xccfccfccfccf; for(int i=1; i<=n; ++i) { ans=min(ans,max(abs(c-a[i].x)+dp[i][i][0],0ll)); } cout<<ans<<"\n"; return 0; }对了顺便提一句,如果写了函数一定要看有没有 return 或有没有 void,因为如果其他类型的函数没有返回值的话,他就会跳转到一个未知的地方导致堆栈错误,Windows系统没事,但是Linux系统下就爆炸。
然后我一开始想二分,于是把DP写到bool类型的check函数里,后来发现不用二分然后忘了改类型,于是喜提0分的好成绩。
吃一堑,饱一顿 ——Dzl
在编译器选项里面加入命令
-Wall可以有效避免此类型的UB -
0
先说复杂度: 可优化为
一、转化问题
看到这题的题面,突然想到了 P2466 [SDOI2008] Sue 的小球 这道题(核心内容没什么关系,不要被误导)
那么,相同点(废话):
- 都在数轴上
- 数轴上都有一些点要使用
- 对于每样东西都分为可用阶段和不可用阶段 且两阶段单调
相似点:
- Sue这道题是小球会下落,落到某时刻就捡不到了,即先有用再没用
- 而本题则是打卡点在t[i]时刻才会开启,即先没用再有用。
可以发现这是相反的,那么考虑将过程反过来做(以下就都已经反过来了)
现在问题变成了 一开始打卡点随便选,但在某时刻后打卡点会失效。设初始时间为time,最终时间是0
二、设置状态,考虑转移
现在,如果做过Sue这道题,可以直接套用dp状态
如果没做过,那我们来寻找一些性质:因为现在打卡点随便选,所以当我们经过一段区间时,区间内所有能用的点我们都会用,且最后定停在某一端上,自然可以区间dp,设f[i][j][0/1],表示已选完i到j个打卡点,目前停在i/j点的最小时间
转移:
f[i][j][0] <- f[i+1][j][0]+d[i][i+1] 或 f[i+1][j][1]+dxx
f[i][j][1] <- f[i][j-1][0]+dxx 或 f[i][j-1][1]+dxx
另外
- 对于每一个区间,真正的答案是: 最大的 (区间内某点到达时间+t[此点] ),这个可以在转移时更新
- 因为本题要求所有点都必须选,所以转移时要判断能否选,不能选就不转移了
- 因为是倒着考虑,所以最开始在c点(赋初值),最后要去0点(最后答案)
- 我们不知道什么时候结束,即time是多少,所以二分(好像不二分也行,可以把time设的很大,然后直接跑一遍出答案)
三、具体见代码:
f[][][].F记录在某一端点的到达时间,f[][][].S记录区间真答案
k1k2那一堆是转移的一大坨,太长所以换成k1k2了
#include<bits/stdc++.h> #define F first #define S second using namespace std; struct node{ int x,t; bool operator<(const node y)const{ // if(x==y.x) return t<y.t; return x<y.x; } }d[1005]; int n,L,c,mx; pair<int,int> f[1005][1005][5]; bool check(int tim){ // cout<<tim<<' '; memset(f,0x3f,sizeof f); for(int i=1;i<=n;i++){ if(abs(d[i].x-c)<=tim-d[i].t+1){ f[i][i][0].F=f[i][i][1].F=abs(d[i].x-c); f[i][i][0].S=f[i][i][1].S=abs(d[i].x-c)+d[i].t; } } for(int len=2;len<=n;len++){ for(int i=1;i+len-1<=n;i++){ int j=i+len-1; int k1=f[i+1][j][0].F+d[i+1].x-d[i].x; int k2=f[i+1][j][1].F+d[j].x-d[i].x; int k3=f[i][j-1][0].F+d[j].x-d[i].x; int k4=f[i][j-1][1].F+d[j].x-d[j-1].x; if(k1<=tim-d[i].t){ if(f[i][j][0].F>=k1){ f[i][j][0].F=k1; f[i][j][0].S=min(f[i][j][0].S,f[i+1][j][0].S); } } if(k2<=tim-d[i].t){ if(f[i][j][0].F>=k2){ f[i][j][0].F=k2; f[i][j][0].S=min(f[i][j][0].S,f[i+1][j][1].S); } } if(k3<=tim-d[j].t){ if(f[i][j][1].F>=k3){ f[i][j][1].F=k3; f[i][j][1].S=min(f[i][j][1].S,f[i][j-1][0].S); } } if(k4<=tim-d[j].t){ if(f[i][j][1].F>=k4){ f[i][j][1].F=k4; f[i][j][1].S=min(f[i][j][1].S,f[i][j-1][1].S); } } f[i][j][0].S=max(f[i][j][0].S,f[i][j][0].F+d[i].t); f[i][j][1].S=max(f[i][j][1].S,f[i][j][1].F+d[j].t); // cout<<i<<' '<<j<<' '<<f[i][j][0]<<' '<<f[i][j][1]<<'\n'; } } int ans1=max(f[1][n][0].S,f[1][n][0].F+d[1].x); int ans2=max(f[1][n][1].S,f[1][n][1].F+d[n].x); // cout<<ans1<<' '<<ans2<<'\n'; return tim>=min(ans1,ans2); } int main() { freopen("1.in","r",stdin); freopen("1.out","w",stdout); cin>>n>>L>>c; for(int i=1;i<=n;i++){ cin>>d[i].x>>d[i].t; mx=max(mx,d[i].t); } sort(d+1,d+n+1); int l=mx,r=12000,mid,ans=-1; while(l<=r){ mid=(l+r)/2; if(check(mid)) r=mid-1,ans=mid; else l=mid+1; } cout<<ans; return 0; }四、比答案少一怎么办1、更新真答案的时候,59行写了吗
2、判断能否转移时,k<=tim-t[i] +1 ,不用+1
- 1
信息
- ID
- 301
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- (无)
- 递交数
- 34
- 已通过
- 11
- 上传者