这道题的思路其实就是转化题意+01背包。
首先 k 应该是最好想的,随便乱搞都能求出来(自己想一想,肯定能想出来)。这里就提醒一句:按照容积从大到小排个序,之后暴力往前倒水就行了。
其次,倒来倒去,t 实际上就是移动了的部分的总体积和,t 最小就意味着没有移动的部分体积最大。那么 t 就完全可以用01背包求出来了。dp[i][j] 表示当前选了 i 个瓶子,总容积为 j 时的最小总体积和。那么用01背包的思路去想,把每一个 b[i] 当做价值,把每一个 a[i] 当做花费,直接做一遍01背包,就可以算出合法状态下没有移动的部分的最大体积和了。
那么还有一个细节问题:dp[i][j] 中的 j 应该枚举到几呢?实际上就是求 k 时枚举到的前若干位的容积之和 lim。因为取 k 个瓶子,总容积一定不会超过 lim,所以枚举答案时也是从水量总和(因为装下这么多水的总容积至少是这个水量总和)到 lim 就可以了。
最后说一句,不要以为 dp 是两维的,计算 dp 时就只枚举 i 和 j——因为 i 只有 1~k,所以必须枚举第三个变量表示当前是第几瓶才行(我当时就这样悲剧了)。还有,j一定要记得倒序枚举。
下面就直接上代码吧:
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int n,K,tot=0,ans,lim,a[105],b[105],dp[105][10005];
struct nod
{
int a,b,c;
}t[105];
void update(int now) //update 跟线段树毛线关系都没有,只是求第 now 位上的空余量而已
{
t[now].c=t[now].b-t[now].a;
}
void pour(int i,int j) //把第 i 瓶水倒进第 j 瓶水
{
if(t[j].c>=t[i].a) t[j].a+=t[i].a,t[i].a=0,update(j),update(i);
else t[i].a-=t[j].c,t[j].a=t[j].b,update(j),update(i);
}
bool cmp(nod i,nod j) //按照容积从大到小排序
{
return i.b==j.b?i.a>j.a:i.b>j.b;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],t[i].a=a[i],tot+=a[i];
for(int i=1;i<=n;i++) cin>>b[i],t[i].b=b[i],update(i);
sort(t+1,t+n+1,cmp);
int head=1,tail=n;
while(head<tail)
{
if(t[head].c>=t[tail].a) pour(tail,head),tail--;
else pour(tail,head),head++;
} //暴力往前倒水
K=tail; //简单粗暴的求出 K
cout<<K<<" ";
for(int i=1;i<=K;i++) lim+=t[i].b;
memset(dp,-12,sizeof(dp));dp[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=K;j>=1;j--)
for(int k=lim;k>=b[i];k--)
dp[j][k]=max(dp[j][k],dp[j-1][k-b[i]]+a[i]); //转移
for(int i=lim;i>=tot;i--) //tot~lim 这一段都有可能是答案
ans=max(ans,dp[K][i]);
cout<<tot-ans; //别忘了 ans 只是合法状态下没有移动的部分的最大体积和,t=tot-ans
}