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的价值
时间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';
}