Get Many Cola_abc415_g

59 阅读4分钟

upd:25/10/05

注:官方题解程序与其题解给的性质不符合。

结合下图看看这一过程,因为除了喝一开始的所有满瓶外,价值都是由空瓶的减少产生的,所以应该一开始喝掉所有满瓶,得到N个空瓶(这样使得一开始空瓶数目最大,之后的可操作空间最大),之后进行以下过程:

f987766eeaa6d056ab028c69d29589e.jpg

把这个过程逆过来看,若当前的空瓶数量是x:如果x>=b_i,则可以将x变为x+d_i(其中,d_i=a_i-b_i),同时其实也需要x+d_i<=n。

这就把这个问题转化为了“完全背包问题”,背包容量是n,每个物品的重量是d_i,价值是b_i。

有多少个物品呢?正着想,对相同的a_i,最大的b_i收益最大,所以a_i的范围在1到300,那么物品数量m其实不会超过300。

但是背包容量比较大,n最大达到了1e15,完全背包常规算法的复杂度是n*m,会超时。

这里有一个性质:在m个物品中,取b_i/d_i最大的(这个物品感性上收益率最大),最大的物品编号是tar,那么在最优解中,除了tar外,使用其余物品所能达到的最大重量其实小于lim=maxa*(maxa+1)。

证明:

记K=maxa,那么lim=K*(K+1),记空瓶数量为x,当空瓶数量第一次增加到大于等于K时,一定有x属于[K,2K-2],因为x一次最多增加(K-1),当x<K时x最大为K-1时,最大增加到2K-2,K>=2保证K<=2K-2,也就是这个区间恒合法。此时,因为K>任意B_i,所有物品接下来都可以填充,并且由于至少还有K(K+1)-(2K-2)=K(K-1)+2的质量空缺,这说明至少还能填入K个非tar物品,记前缀和s(i)=第1到i个非tar物品的重量和,则在s(0),s(1).....s(K)中有K+1个数,D_tar=A_tar-B_tar<=K-1,则一个数除以D_tar的余数最多只有K-1个,而现在有K+1个s,则根据鸽巢原理,必定有s(l)同余于s(r),(l<r),则s(r)-s(l)是D_tar的倍数,将第l+1到第r个,这些非tar物品换成tar,总重量不变,但得到不会变少的价值,因此只使用其余物品所能达到的最大重量不会大于等于K(K+1),得证。

当n<=lim时,用普通的完全背包算法。当n>lim时,最优解一定是非tar物品的重量小于lim,故只在0到lim范围内不用tar进行完全背包,然后i从0到lim遍历,剩余的lim-i的重量空间全部用tar填充,得到每个总价值,在其中取最大作为答案。

不要忘记,不论逆过程最终有多少个空瓶子,初始的n个饮料都要喝光。

#include<bits/stdc++.h>
using namespace std;
using ll=long long ;

#define fio ios::sync_with_stdio(0);cin.tie(0);
#define fin freopen("D:/in.txt","r",stdin);
#define fout freopen("D:/out.txt","w",stdout);

const ll maxn=2e5+5;
ll n,m,maxa;
struct Method{
    ll a,b,d;
}me[maxn];
ll loc[300+5],dp[(ll)1e5+5];

int main()
{
    cin>>n>>m;
    ll t=0,maxa=0;
    for(ll i=1;i<=m;i++) {
        ll a,b;cin>>a>>b;
        maxa=max(maxa,a);
        if(loc[a]==0) {
            t++;
            loc[a]=t;
            me[t].a=a;
            me[t].b=b;
            me[t].d=a-b;
        }else {
            if(b>me[loc[a]].b) {
                me[loc[a]].b=b;
                me[loc[a]].d=a-b;
            }
        }
    }
    m=t;

    ll lim=maxa*(maxa+1);
    //puts("finished!");

    if(n<=lim) {
        for(ll i=0;i<=n;i++) {
            for(ll j=1;j<=m;j++) {
                ll d=me[j].a-me[j].b;
                if(i>=d && i-d>=me[j].b) {
                    dp[i]=max(dp[i],dp[i-d]+me[j].b);
                }
            }
        }
        cout<<n+dp[n]<<"\n";
    }
    else {
        //找b_i/d_i最大的
        ll tar=1;
        for(ll i=1;i<=m;i++) {
            if(me[i].b*me[tar].d>me[tar].b*me[i].d) {
                tar=i;
            }
        }

        //先不用tar做一次背包,计算0到lim内的dp值
        for(ll i=0;i<=lim;i++) {
            for(ll j=1;j<=m;j++) {
                if(j==tar) continue;
                ll d=me[j].a-me[j].b;
                if(i>=d && i-d>=me[j].b) {
                    dp[i]=max(dp[i],dp[i-d]+me[j].b);
                }
            }
        }


        ll ans=0;
        for(ll i=0;i<=lim;i++) {
            if(i>=me[tar].b)
            ans=max(ans,dp[i]+(n-i)/me[tar].d*me[tar].b);
        }
        cout<<ans+n<<"\n";
    }

    return 0;
}