【题解】 CF730J Bottles

这道题的思路其实就是转化题意+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
#include
#include
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(headif(t[head].c>=t[tail].a)    pour(tail,head),tail--;
        else    pour(tail,head),head++;
    }   //暴力往前倒水
    K=tail; //简单粗暴的求出 K
    cout<" ";
    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<//别忘了 ans 只是合法状态下没有移动的部分的最大体积和,t=tot-ans
}

你可能感兴趣的:(题解)