设$dp[l][r][p]$为走完区间$[l,r]$,在端点$p$时所需的最短时间($p=0$代表在左端点,$p=1$代表在右端点)
根据题意显然有状态转移方程$left{egin{matrix}dp[l][r][0]=min(dp[l+1][r][0]+x[l+1]-x[l],dp[l+1][r][1]+x[r]-x[l]);\ dp[l][r][1]=min(dp[l][r-1][0]+x[r]-x[l],dp[l][r-1][1]+x[r]-x[r-1]);end{matrix} ight.$
复杂度是$O(n^2)$的,对于10000的数据量还是有些勉强,因此某些常数比较大的算法会T掉。
为此我测试了多种不同的算法,比较了一下它们各自的优劣。
首先是最普通的区间dp:(440ms)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=10000+5,inf=0x3f3f3f3f; 5 int dp[N][N][2],n,x[N],t[N],ans; 6 7 int main() { 8 while(scanf("%d",&n)==1) { 9 for(int i=0; i<n; ++i)scanf("%d%d",&x[i],&t[i]); 10 for(int i=0; i<n; ++i)dp[i][i][0]=dp[i][i][1]=0; 11 for(int l=n-1; l>=0; --l) 12 for(int r=l+1; r<n; ++r) { 13 dp[l][r][0]=min(dp[l+1][r][0]+x[l+1]-x[l],dp[l+1][r][1]+x[r]-x[l]); 14 dp[l][r][1]=min(dp[l][r-1][0]+x[r]-x[l],dp[l][r-1][1]+x[r]-x[r-1]); 15 if(dp[l][r][0]>=t[l])dp[l][r][0]=inf; 16 if(dp[l][r][1]>=t[r])dp[l][r][1]=inf; 17 } 18 int ans=min(dp[0][n-1][0],dp[0][n-1][1]); 19 if(ans==inf)printf("No solution "); 20 else printf("%d ",ans); 21 } 22 return 0; 23 }
注意l要从右往左循环就行。
然后是用滚动数组优化后的dp:(290ms)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=10000+5,inf=0x3f3f3f3f; 5 int dp[2][N][2],n,x[N],t[N],ans; 6 7 int main() { 8 while(scanf("%d",&n)==1) { 9 for(int i=0; i<n; ++i)scanf("%d%d",&x[i],&t[i]); 10 memset(dp,0,sizeof dp); 11 for(int l=n-1; l>=0; --l) 12 for(int r=l+1; r<n; ++r) { 13 dp[l&1][r][0]=min(dp[l&1^1][r][0]+x[l+1]-x[l],dp[l&1^1][r][1]+x[r]-x[l]); 14 dp[l&1][r][1]=min(dp[l&1][r-1][0]+x[r]-x[l],dp[l&1][r-1][1]+x[r]-x[r-1]); 15 if(dp[l&1][r][0]>=t[l])dp[l&1][r][0]=inf; 16 if(dp[l&1][r][1]>=t[r])dp[l&1][r][1]=inf; 17 } 18 int ans=min(dp[0][n-1][0],dp[0][n-1][1]); 19 if(ans==inf)printf("No solution "); 20 else printf("%d ",ans); 21 } 22 return 0; 23 }
和上面的代码唯一的不同之处就是l换成了l&1,l+1换成了l&1^1。
其他算法:
bfs刷表法:(1940ms)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=10000+5,inf=0x3f3f3f3f; 5 int dp[2][N][2],vis[2][N][2],n,x[N],t[N],ans,ka; 6 struct D {int l,r,p;}; 7 queue<D> q; 8 9 void upd(int l,int r,int p,int ad) { 10 if(vis[l&1][r][p]!=l)vis[l&1][r][p]=l,dp[l&1][r][p]=inf,q.push({l,r,p}); 11 if(dp[l&1][r][p]>ad)dp[l&1][r][p]=ad; 12 } 13 14 int bfs() { 15 memset(vis,-1,sizeof vis); 16 int ret=inf; 17 while(!q.empty())q.pop(); 18 for(int i=0; i<n; ++i)upd(i,i,0,0); 19 while(!q.empty()) { 20 int l=q.front().l,r=q.front().r,p=q.front().p; 21 q.pop(); 22 if(l==0&&r==n-1) {ret=min(ret,dp[l&1][r][p]); continue;} 23 int P=p==0?l:r; 24 if(l>0&&dp[l&1][r][p]+x[P]-x[l-1]<t[l-1])upd(l-1,r,0,dp[l&1][r][p]+x[P]-x[l-1]); 25 if(r<n-1&&dp[l&1][r][p]+x[r+1]-x[P]<t[r+1])upd(l,r+1,1,dp[l&1][r][p]+x[r+1]-x[P]); 26 } 27 return ret; 28 } 29 30 int main() { 31 while(scanf("%d",&n)==1) { 32 ++ka; 33 for(int i=0; i<n; ++i)scanf("%d%d",&x[i],&t[i]); 34 int ans=bfs(); 35 if(ans==inf)printf("No solution "); 36 else printf("%d ",ans); 37 } 38 return 0; 39 }
这个算法对于本题来说常数比较大,必须要用滚动数组优化。
dfs记忆化搜索法:(1580ms)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=10000+5,inf=0x3f3f3f3f; 5 int dp[N][N][2],vis[N][N],n,x[N],t[N],ans,ka; 6 7 void dfs(int l,int r) { 8 if(vis[l][r]==ka)return; 9 vis[l][r]=ka; 10 if(l==r)dp[l][r][0]=dp[l][r][1]=0; 11 else { 12 dfs(l+1,r),dfs(l,r-1); 13 dp[l][r][0]=min(dp[l+1][r][0]+x[l+1]-x[l],dp[l+1][r][1]+x[r]-x[l]); 14 dp[l][r][1]=min(dp[l][r-1][0]+x[r]-x[l],dp[l][r-1][1]+x[r]-x[r-1]); 15 if(dp[l][r][0]>=t[l])dp[l][r][0]=inf; 16 if(dp[l][r][1]>=t[r])dp[l][r][1]=inf; 17 } 18 } 19 20 int main() { 21 while(scanf("%d",&n)==1) { 22 ++ka; 23 for(int i=0; i<n; ++i)scanf("%d%d",&x[i],&t[i]); 24 dfs(0,n-1); 25 int ans=min(dp[0][n-1][0],dp[0][n-1][1]); 26 if(ans==inf)printf("No solution "); 27 else printf("%d ",ans); 28 } 29 return 0; 30 }
这个算法常数也比较大,但无法用滚动数组来优化,因此需要额外增设一个vis数组和一个变量ka,来避免dp数组的初始化。
去掉一维直接dp法:(260ms)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=10000+5,inf=0x3f3f3f3f; 5 int dp[N][2],n,x[N],t[N],ans; 6 7 int main() { 8 while(scanf("%d",&n)==1) { 9 for(int i=0; i<n; ++i)scanf("%d%d",&x[i],&t[i]); 10 memset(dp,0,sizeof dp); 11 for(int i=1; i<n; ++i) 12 for(int l=0; l+i<n; ++l) { 13 dp[l][1]=min(dp[l][0]+x[l+i]-x[l],dp[l][1]+x[l+i]-x[l+i-1]); 14 dp[l][0]=min(dp[l+1][0]+x[l+1]-x[l],dp[l+1][1]+x[l+i]-x[l]); 15 if(dp[l][0]>=t[l])dp[l][0]=inf; 16 if(dp[l][1]>=t[l+i])dp[l][1]=inf; 17 } 18 int ans=min(dp[0][0],dp[0][1]); 19 if(ans==inf)printf("No solution "); 20 else printf("%d ",ans); 21 } 22 return 0; 23 }
这个算法无论是时间还是空间的消耗都是最小的,虽然比较抽象。设dp[i][l][p]为区间左端点为l,长度为i+1,当前位置为p时所需的最短时间,则第一维i可以直接去掉。