UVA - 1632 Alibaba (区间dp+常数优化)

题目链接

设$dp[l][r][p]$为走完区间$[l,r]$,在端点$p$时所需的最短时间($p=0$代表在左端点,$p=1$代表在右端点)

根据题意显然有状态转移方程$\left\{\begin{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}\right.$

复杂度是$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\n");
20         else printf("%d\n",ans);
21     }
22     return 0;
23 }
View Code

注意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\n");
20         else printf("%d\n",ans);
21     }
22     return 0;
23 }
View Code

和上面的代码唯一的不同之处就是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\n");
36         else printf("%d\n",ans);
37     }
38     return 0;
39 }
View Code

这个算法对于本题来说常数比较大,必须要用滚动数组优化。

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\n");
27         else printf("%d\n",ans);
28     }
29     return 0;
30 }
View Code

这个算法常数也比较大,但无法用滚动数组来优化,因此需要额外增设一个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\n");
20         else printf("%d\n",ans);
21     }
22     return 0;
23 }
View Code

这个算法无论是时间还是空间的消耗都是最小的,虽然比较抽象。设dp[i][l][p]为区间左端点为l,长度为i+1,当前位置为p时所需的最短时间,则第一维i可以直接去掉。

猜你喜欢

转载自www.cnblogs.com/asdfsag/p/10448298.html