【dp】【优化】Problem 791: 硬币

10 篇文章 0 订阅
4 篇文章 0 订阅

Problem 791: 硬币

Time Limit: 2000 ms Memory Limit: 524288 KB

Problem Description
有X+Y+Z个人,从1到X+Y+Z编号。第i个人有Ai个金币,Bi个银币,Ci个铜币。

小J想从X个人手中得到他们的全部金币,Y个人手中得到他们的全部银币,Z个人手中得到他们的全部铜币。

一个人只会将他的一种硬币全部给小J。

小J想知道她最多能得到多少个硬币。

【数据范围及子任务】

1≤X

1≤Y

1≤Z

X+Y+Z≤10^5

1≤Ai≤10^9

1≤Bi≤10^9

1≤Ci≤10^9

Subtask1(8分):X+Y+Z≤14;

Subtask2(12分):X+Y+Z≤20

Subtask3(8分):所有的Ai=Bi

Subtask4(12分):X+Y+Z≤1000

Subtask5(16分):X+Y+Z≤5000

Subtask6(44分):无特殊限制

Input
第一行读入三个整数X,Y,Z

接下来X+Y+Z行,每行三个整数Ai,Bi,Ci

形如:

X Y Z

A1 B1 C1

A2 B2 C2

:

AX+Y+Z BX+Y+Z CX+Y+Z

Output
输出一行一个整数,表示小J最多能得到多少硬币。

Sample Input
【样例输入1】

1 2 1

2 4 4

3 2 1

7 6 7

5 2 3

【样例输入2】

3 3 2

16 17 1

2 7 5

2 16 12

17 7 7

13 2 10

12 18 3

16 15 19

5 6 2

【样例输入3】

6 2 4

33189 87907 277349742

71616 46764 575306520

8801 53151 327161251

58589 4337 796697686

66854 17565 289910583

50598 35195 478112689

13919 88414 103962455

7953 69657 699253752

44255 98144 468443709

2332 42580 752437097

39752 19060 845062869

60126 74101 382963164

Sample Output
【样例输出1】

18

【样例输出2】

110

【样例输出3】

3093929975

【样例解释】

对于样例1:

从第一个人那里获得银币,第二个人那里获得银币,第三个人那里获得铜币,第四个人那里获得金币。这样,硬币的总数为4+2+7+5=18。没有任何一种方案能得到多于18个硬币。

Problem Source
dhr

题解

3维dp很好想

if(j)dp[j][k][i-j-k]=max(dp[j-1][k][i-j-k]+o,dp[j][k][i-j-k]);
if(k)dp[j][k][i-j-k]=max(dp[j][k][i-j-k],dp[j][k-1][i-j-k]+p);
if(i-j-k)dp[j][k][i-j-k]=max(dp[j][k][i-j-k],dp[j][k][i-j-k-1]+q);

但是只能得20分(雾)
然后就要优化

对于Subtask4(12分):X+Y+Z≤1000
只用开O(1000 * 500 * 333)就行了

对于Subtask3(8分):所有的Ai=Bi
说明题目由三种硬币转化成了两种硬币(设为a[i]和b[i])
我们可以把a[i]-b[i]从大到小排序
前x个取a,后y个取b

再推广到三种硬币
如果我们能确定c取哪z个
就转化为两种硬币了

用f[i][j]表示做到第i个数时取了j个c的价值

f[i][j]=ic?f[i1][j1]+c:(ij<=x?f[i1][j]+a:f[i1][j]+b)

时间O(n^2)
空间O(n) //空间可以滚动掉一维

以上内容与正解无关,仅是优化

正解:
先按a[i]-b[i]从大到小排序
我们可以枚举一条分界线
前面都是a与c
后面都是b与c
时间O(n)枚举分界线
每次排序会超时
所以用小根堆(优先队列)
维护x个a[i]-c[i]最大值
记录前缀的价值
反过来从后往前再做一次
维护y个b[i]-c[i]最大值
记录后缀的价值
最后再扫一遍统计答案
时间O(nlogn)
空间O(n)

参考文献

[1]AtCoder Grand Contest 018 做题记录
[2]cs的比赛总结

正解

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#define N 100010
using namespace std;
typedef long long LL;
priority_queue<int,vector<int>,greater<int> >Q;
LL ans,l[N],r[N];
struct Coin{
    int p,q,o;
}a[N];
bool cmp(Coin p,Coin q){
    return p.p-p.q>q.p-q.q;
}
int x,y,z,n;
int main(){
    freopen("data.txt","r",stdin);
    scanf("%d%d%d",&x,&y,&z);
    n=x+y+z;
    for(int i=1;i<=n;i++)scanf("%d%d%d",&a[i].p,&a[i].q,&a[i].o);
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=x;i++){
        Q.push(a[i].p-a[i].o);
        l[i]=l[i-1]+a[i].p;
    }
    for(int i=x+1;i<=n;i++){
        if(a[i].p-a[i].o>Q.top()){
            l[i]=l[i-1]-Q.top()+a[i].p;
            Q.pop();
            Q.push(a[i].p-a[i].o);
        }
        else l[i]=l[i-1]+a[i].o;
    }
    while(!Q.empty())Q.pop();
    for(int i=n;i>=n-y+1;i--){
        r[i]=r[i+1]+a[i].q;
        Q.push(a[i].q-a[i].o);
    }
    for(int i=n-y;i>=1;i--){
        if(a[i].q-a[i].o>Q.top()){
            r[i]=r[i+1]-Q.top()+a[i].q;
            Q.pop();
            Q.push(a[i].q-a[i].o);
        }
        else r[i]=r[i+1]+a[i].o;
    }
    for(int i=x;i<=n-y;i++)ans=max(ans,l[i]+r[i+1]);
    printf("%lld\n",ans);
}

3维dp(20分)

#include<cstdio>
#include<iostream>
#define N 110
#define INF 9000000000000000000
using namespace std;
typedef long long LL;
int x,y,z,o,p,q;
LL dp[N][N][N];
int main(){
    scanf("%d%d%d",&x,&y,&z);
    for(int i=1;i<=x+y+z;i++){
        scanf("%d%d%d",&o,&p,&q);
        for(int j=min(x,i);j>=0;j--){
            for(int k=min(i,y);k>=0;k--){
                if(i-j-k<0||i-j-k>z)continue;
                if(j)dp[j][k][i-j-k]=max(dp[j-1][k][i-j-k]+o,dp[j][k][i-j-k]);
                if(k)dp[j][k][i-j-k]=max(dp[j][k][i-j-k],dp[j][k-1][i-j-k]+p);
                if(i-j-k)dp[j][k][i-j-k]=max(dp[j][k][i-j-k],dp[j][k][i-j-k-1]+q);
            }
        }
    }
    cout<<dp[x][y][z]<<'\n';
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值