fjnu2019第二次友谊赛 F题

### 题目链接 ###

 

题目大意:

 

一开始手上有 z 个钱币,有 n 天抉择,m 种投资方案,在每天中可以选择任意种投资方案、任意次地花费 x 个钱币(手上的钱币数不能为负),使得在 n 天结束后,获得 y 个钱币。

 

其次,在每天结束后,会根据自己手上所具有的节点数来获得一些钱币补偿,设当天结束后所拥有 x 个钱币,那么将获得 f(x) 个钱币,若 x > k ,则默认为 0 。保证 f[0]~f[k] 单调不增。

 

这题的原题:博客连接 ,只是一开始手上的钱数不同而已。

 

分析:

1、将全过程分为 n 天,在 dp 枚举每天的状态时,先处理当天一开始所拥有的钱币数(即上一天结束后手上的钱数),再进行当天的投资。所以我们将当天投资结束后的补偿状态在下一天的早上进行转移,而不是在当天结束后进行状态转移。

2、故设 dp[i][j] 表示在第 i 天投资完之后(当天还未获得补偿),在手上拥有 j 个钱币时,在 n 天结束后所获得的返还的最大钱币总数。

3、那么对于每一天的一开始钱币数是由上一天补偿之后而转移过来的,由于补偿只会在有剩下 0 ~ k 个钱币时才会发生 (即 手上钱币数为 0 ~ k 时才可能获得补偿),故在每天开始前需要遍历上一天手中剩下的钱币数,获得一定的补偿之后,成为今天一开始的钱币数。

4、获得当天的钱币数后,开始进行今天的投资状态转移。由于投资的数量为任意次,故为完全背包的转移。

5、若对于当天投资结束后手上有 j 元时的状态( 即 dp[i][j] ),它必从当天投资一开始的手上钱币为 ( j + 投资成本 )时,花费投资成本后转移而来。故若设投资成本为 w ,最后一天返还 v ,则有 dp[i][j] = dp[i][j + w] + v ,完全背包取最大值即可。

6、由于一开始有钱币数,而按上面所述,第一天一开始不能从上一天剩余钱币数上转移,故第一天需要单独拿出来先处理。

7、初始化问题:按理这题 dp 需要求最大值,全部初始化为 0 才对,但是需要保证本题的实际意义——需要从第一天手上 z 个钱币时转移,故需要初始化全部为负无穷,然后将dp[1][z] 赋值为 0 ,表明 dp 转移时,必须从第一天手上有 z 个钱币时转移而来。

8、由于我们将第一天枚举单独拿出来了,即意味着求的所有状态都必须在第一天买东西才会被转移到第二天(即第一天啥都不买时,这个状态不会被传递下去),然后再进行下面的每天转移。故我们求答案时,还需要判断是否 n 天啥都不投资的钱币会更多(意思是如果第一天啥都不买,最大值不可能轮到第二天才开始买)。

 

注意点:

1、若按上述这样直接做的话,复杂度接近 n³ * k (k 为常数),此题数据将会有 3000MS (当然出题人良心放宽时限)。

2、若要降低时间复杂度,需要知道完全背包去掉枚举方案个数的原理。这个原理其实是当前 dp[i][j] 从本层之前已更新过的状态转移过来(详细则自己百度或群里问)。

3、此题与普通完全背包降维不同的是,分析 dp 转移方程,发现他是从后往前转移的 (即 dp[i][j] 中的 j 从 j + w 上转移而来),优化时,需要反向枚举背包容量。

 

无优化代码:

#include
#include
#include<string.h>
using namespace std;
int z,N,M,K;
int dp[108][2008];
int f[1008];
struct Goods{
    int a,b;
}A[108];
int main()
{
    scanf("%d%d%d%d",&z,&N,&M,&K);
    for(int i=0;i<=K;i++) scanf("%d",&f[i]);
    for(int i=1;i<=M;i++) scanf("%d%d",&A[i].a,&A[i].b);
    memset(dp,0x80,sizeof(dp));
    dp[1][z]=0;
    for(int w=1;w<=M;w++){
        for(int j=A[w].a;j<=2000;j++){
            for(int k=0;k<=j/A[w].a;k++){
                dp[1][j-k*A[w].a]=max(dp[1][j-k*A[w].a],dp[1][j]+k*A[w].b);
            }
        }
    }
    for(int i=2;i<=N;i++){
        for(int j=0;j<=K;j++) dp[i][j+f[j]]=max(dp[i][j+f[j]],dp[i-1][j]);
        for(int w=1;w<=M;w++){
            for(int j=A[w].a;j<=2000;j++){
                for(int k=0;k<=j/A[w].a;k++){
                    dp[i][j-k*A[w].a]=max(dp[i][j-k*A[w].a],dp[i][j]+k*A[w].b);
                }
            }
        }
    }
    int ans=z;
    for(int i=0;i<=2000;i++) ans=max(ans,dp[N][i]+i+f[i]);
    printf("%d\n",ans);
}

 

优化代码:

#include
#include
#include<string.h>
using namespace std;
int z,N,M,K;
int dp[108][2008];
int f[1008];
struct Goods{
    int a,b;
}A[108];
int main()
{
    scanf("%d%d%d%d",&z,&N,&M,&K);
    for(int i=0;i<=K;i++) scanf("%d",&f[i]);
    for(int i=1;i<=M;i++) scanf("%d%d",&A[i].a,&A[i].b);
    memset(dp,0x80,sizeof(dp));
    dp[1][z]=0;
    for(int w=1;w<=M;w++){
        for(int j=2000;j>=A[w].a;j--){
            dp[1][j-A[w].a]=max(dp[1][j-A[w].a],dp[1][j]+A[w].b);
        }
    }
    for(int i=2;i<=N;i++){
        for(int j=0;j<=K;j++) dp[i][j+f[j]]=max(dp[i][j+f[j]],dp[i-1][j]);
        for(int w=1;w<=M;w++){
            for(int j=2000;j>=A[w].a;j--){
                dp[i][j-A[w].a]=max(dp[i][j-A[w].a],dp[i][j]+A[w].b);
            }
        }
    }
    int ans=z;
    for(int i=0;i<=2000;i++) ans=max(ans,dp[N][i]+i+f[i]);
    printf("%d\n",ans);
}

 

 

你可能感兴趣的:(fjnu2019第二次友谊赛 F题)