JOISC2014 挂饰("01"背包)

传送门:

  [1]:洛谷

  [2]:BZOJ

 

参考资料:

  [1]:追忆:往昔 

 

题解

  上述参考资料的讲解清晰易懂,下面谈谈我的理解;

  关键语句:

  

  将此题转化为 "01背包" 类问题,关键就是上述语句;

  据此,定义 dp[ i ][ j ] 表示前 i 个物品在钩子剩余 j 个的状态下所获得的最大喜悦值;

  细节处理:

  

  为了应对负数的情况,让 dp[ i ][ j ] 的下标 j 全部增加 2000 ,这样就可以表示在负数范围内的值了。

  状态转移:

  首先,初始化 dp[][] 数组为 -INF,并令 dp[0][2001]=0(初始手机上含有一个挂钩);

 1 for(int i=1;i <= n;++i)
 2 {
 3     a[i]--;
 4     for(int j=1;j <= 4000;++j)
 5     {
 6         dp[i][j]=max(dp[i][j],dp[i-1][j]);
 7         if(dp[i][j] == -INF)
 8             continue;
 9 
10         int k=min(j+a[i],4000);
11         dp[i][k]=max(dp[i][k],max(dp[i-1][j]+b[i],dp[i-1][k]));
12     }
13 }    

  解释1:挂饰 i 可以提供 ai 个挂钩,但是,要把它挂到手机上需要消耗一个挂钩,所以,挂饰 i 额外提供 ai-1 个挂钩;

  解释2:第6行的更新是必不可少的,手动测试如下样例便可明白:

5
0 0
0 0
0 0
0 0
0 10

  解释3:第 11 行,dp[ i ][ k ] 只受状态 dp[ i-1 ][ j ] 和 dp[ i-1 ][ k ] 的影响,为什么在判断的时候需要额外与 dp[ i ][ k ] 判断呢?

       仔细看一下 k,如果 j+a[ i ] 超过上界 4000,那么 k = 4000,对于超过 4000 的肯定都更新到了 dp[ i ][ 4000 ]上;

       所以,dp[ i ][ 4000 ] 会首其他状态的影响,而不只是上述两种状态的影响;

  验证解释3的正确性:

  将第 11 行代码改成如下所示代码(AC):

1 if(k < 4000)
2     dp[i][k]=max(dp[i-1][j]+b[i],dp[i-1][k]);
3 else
4     dp[i][k]=max(dp[i][k],max(dp[i-1][j]+b[i],dp[i-1][k]));

  而改成如下所示代码(WA):

1 dp[i][k]=max(dp[i-1][j]+b[i],dp[i-1][k]);

•Code

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define INF 0x3f3f3f3f
 4 #define mem(a,b) memset(a,b,sizeof(a))
 5 const int maxn=2e3+50;
 6 
 7 int n;
 8 int a[maxn];
 9 int b[maxn];
10 int dp[maxn][maxn<<1];
11 
12 int Solve()
13 {
14     for(int i=0;i <= n;++i)
15         for(int j=0;j <= 4000;++j)
16             dp[i][j]=-INF;
17 
18     dp[0][2001]=0;
19     for(int i=1;i <= n;++i)
20     {
21         a[i]--;///物品i本身需要占用一个钩子,所以其可以提供的钩子个数为a[i]-1
22         for(int j=1;j <= 4000;++j)
23         {
24             dp[i][j]=max(dp[i][j],dp[i-1][j]);
25             
26             if(dp[i][j] == -INF)
27                 continue;
28 
29             int k=min(j+a[i],4000);
30             dp[i][k]=max(dp[i][k],max(dp[i-1][j]+b[i],dp[i-1][k]));
31         }
32     }
33     int ans=0;
34     for(int i=2000;i <= 4000;++i)
35         ans=max(ans,dp[n][i]);
36     return ans;
37 }
38 
39 int main()
40 {
41 //    freopen("C:\\Users\\hyacinthLJP\\Desktop\\in&&out\\contest","r",stdin);
42     scanf("%d",&n);
43     for(int i=1;i <= n;++i)
44         scanf("%d%d",a+i,b+i);
45 
46     printf("%d\n",Solve());
47 
48     return 0;
49 }
二维dp

•利用滚动数组降低dp的维数

  

•Code

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define INF 0x3f3f3f3f
 4 #define mem(a,b) memset(a,b,sizeof(a))
 5 const int maxn=2e3+50;
 6 
 7 int n;
 8 int a[maxn],b[maxn];
 9 int dp[maxn<<1];
10 
11 int Solve()
12 {
13     mem(dp,-INF);
14     dp[2001]=0;
15 
16     for(int i=1;i <= n;++i)
17     {
18         a[i]--;
19         if(a[i] > 0)
20         {
21             for(int j=4000;j >= 0;--j)
22             {
23                 if(dp[j] == -INF)
24                     continue;
25                 int k=min(j+a[i],4000);
26                 dp[k]=max(dp[j]+b[i],dp[k]);
27             }
28         }
29         else
30         {
31             for(int j=0;j <= 4000;++j)
32             {
33                 if(dp[j] == -INF)
34                     continue;
35                 dp[j+a[i]]=max(dp[j]+b[i],dp[j+a[i]]);
36             }
37         }
38     }
39     int ans=0;
40     for(int i=2000;i <= 4000;++i)
41         ans=max(ans,dp[i]);
42 
43     return ans;
44 }
45 int main()
46 {
47     scanf("%d",&n);
48     for(int i=1;i <= n;++i)
49         scanf("%d%d",a+i,b+i);
50 
51     printf("%d\n",Solve());
52 
53     return 0;
54 }
一维dp(滚动数组)

 

posted @ 2019-07-18 10:27  HHHyacinth  阅读(177)  评论(0编辑  收藏  举报