upd:25/10/05
注:官方题解程序与其题解给的性质不符合。
结合下图看看这一过程,因为除了喝一开始的所有满瓶外,价值都是由空瓶的减少产生的,所以应该一开始喝掉所有满瓶,得到N个空瓶(这样使得一开始空瓶数目最大,之后的可操作空间最大),之后进行以下过程:
把这个过程逆过来看,若当前的空瓶数量是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;
}