F - Must Buy 解説 by en_translator
First, let us consider the maximum value when the items are chosen so that the total price is up to \(M\) yen.
This is widely known as a knapsack problem, and can be solved by the following Dynamic Programming (DP):
Define \(dp[i][j]\) \((0\leq i\leq N, 0\leq j\leq M)\) as the maximum total value of (\(0\) or more) items among the \(1\)-st through \(i\)-th whose prices sum to \(j\) yen or below.
For \(i=0\), no items can be chosen, so \(dp[i][j]=0\). For \(i\geq 1\), we consider both cases, whether or not to choose item \(i\), obtaining \(dp[i][j]=\max(dp[i-1][j-P_i]+V_i,dp[i-1][j])\). If \(j<P_i\), then item \(i\) cannot be chosen, so \(dp[i][j]=dp[i-1][j]\). Finally, \(dp[N][M]\) is the answer.
Let \(X\) be the answer obtained by this procedure.
Next, we consider how to classify each item \(i\) \((1\leq i\leq N)\). What we are interested in is:
- From the \((N-1)\) items except for item \(i\), can we choose items so that their prices sum to \(M\) yen or below, and the values sum to \(X\)?
- From the \((N-1)\) items except for item \(i\), can we choose items so that their prices sum to \((M-P_i)\) yen or below, and the values sum to \((X-V_i)\)?
Note that the second condition is equivalent to whether we can choose items from the \(N\) items while choosing item \(i\) so that the total price is \(M\) yen or below and the total value is \(X\) yen.
These criteria determines the classification into A, B, and C as follows:
- Category A: does not satisfy 1, but satisfies 2.
- Category B: satisfies both 1 and 2.
- Category C: satisfies 1, but does not satisfy 2.
One can assert that these categorizations are correct by carefully considering for each pattern containing and not containing item \(i\), whether there is a possible valid choice (such that the total price is \(M\) yen or below and the total value is \(X\)). Note that 1 and 2 are never simultaneously violated, as this contradicts to the definition of \(X\).
Therefore, it is sufficient to determine if each item satisfies 1 and 2. However, naively checking it costs \(O(N^2M)\) time, exceeding the time limit. Instead, define a new DP table considered in reverse order. That is, Define \(dp2[i][j]\) \((1\leq i\leq N+1, 0\leq j\leq M)\) as the maximum total value of (\(0\) or more) items among the \(i\)-st through \(N\)-th whose prices sum to \(j\) yen or below. Then the criteria 1 and 2 for item \(i\) can be rephrased as the following:
- \(\displaystyle\max_{0\leq j\leq M}(dp[i-1][j]+dp2[i+1][M-j])\) equals \(X\).
- \(\displaystyle\max_{0\leq j\leq M-P_i}(dp[i-1][j]+dp2[i+1][M-P_i-j])\) equals \(X-V_i\).
Tables \(dp\) and \(dp2\) can precalculated in \(O(NM)\) time, and the decision above acn be evaluated in \(O(M)\) time for each \(i\) \((1\leq i\leq N)\). Therefore, the entire procedure can be completed in \(O(NM)\) time. Regarding the spacial complexity, assuming using a \(64\)-bit integer type, the required memory can be estimated as \(\times 2\times (N\times M) \sim 800\), which is below the memory limit.
Thus, the problem has been solved.
If the memory limit were more tight, one can compute \(dp2\) halfway while scanning \(dp\) side, thus sacrificing the time complexity while reducing the memory consumption inversely proportionally.
Sample code in C++:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pll pair<ll,ll>
#define rep(i, n) for(int i = 0; i < n; ++i)
#define N (int)1000
#define M (int)5e+4
#define INF (ll)1e+18
int main(void){
int n,m,l,r;
int p[N];
ll v[N];
ll dp[N+1][M+1]={};
ll dp2[N+1][M+1]={};
ll tgt;
string ans="";
cin >> n >> m;
for(int i=0;i<n;i++)cin >> p[i] >> v[i];
for(int i=0;i<n;i++){
for(int j=0;j<=m;j++){
if(j<p[i])dp[i+1][j]=dp[i][j];
if(j>=p[i])dp[i+1][j]=max(dp[i][j],dp[i][j-p[i]]+v[i]);
}
}
for(int i=n-1;i>=0;i--){
for(int j=0;j<=m;j++){
if(j<p[i])dp2[i][j]=dp2[i+1][j];
if(j>=p[i])dp2[i][j]=max(dp2[i+1][j],dp2[i+1][j-p[i]]+v[i]);
}
}
tgt=dp[n][m];
for(int i=0;i<n;i++){
long long mx_woi=0;
for(int j=0;j<=m;j++)mx_woi=max(mx_woi,dp[i][j]+dp2[i+1][m-j]);
long long mx_wi=0;
for(int j=0;j<=m-p[i];j++)mx_wi=max(mx_wi,dp[i][j]+dp2[i+1][m-p[i]-j]);
if(mx_woi<tgt)ans+='A';
else if(mx_wi<tgt-v[i])ans+='C';
else ans+='B';
}
cout<<ans<<endl;
return 0;
}
Sample code in C++ (with less memory consumption):
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i, n) for(int i = 0; i < n; ++i)
#define N (int)1000
#define K (int)100
#define M (int)50000
int main(void){
int n,m,l,r;
int p[N];
ll v[N];
ll dp[K+1][M+1];
ll cdp[M+1];
ll tgt;
string ans="";
cin>>n>>m;
rep(i,n)cin>>p[i]>>v[i];
rep(j,m+1)cdp[j]=0;
rep(i,n){
for(int j=m;j>=p[i];j--)cdp[j]=max(cdp[j],cdp[j-p[i]]+v[i]);
}
tgt=cdp[m];
rep(i,n)ans+='X';
rep(j,m+1)dp[0][0]=0;
rep(k,(n-1)/K+1){
r=n-k*K-1;
l=max(n-k*K-K,0);
for(int i=r;i>=l;i--){
rep(j,M+1){
dp[r-i+1][j]=dp[r-i][j];
if(j>=p[i])dp[r-i+1][j]=max(dp[r-i+1][j],dp[r-i][j-p[i]]+v[i]);
}
}
rep(j,m+1)cdp[j]=0;
rep(i,l){
for(int j=m;j>=p[i];j--)cdp[j]=max(cdp[j],cdp[j-p[i]]+v[i]);
}
for(int i=l;i<=r;i++){
ll mx_woi=0;
rep(j,m+1)mx_woi=max(mx_woi,cdp[j]+dp[r-i][m-j]);
ll mx_wi=0;
rep(j,m-p[i]+1)mx_wi=max(mx_wi,cdp[j]+dp[r-i][m-p[i]-j]);
if(mx_woi<tgt)ans[i]='A';
else if(mx_wi<tgt-v[i])ans[i]='C';
else ans[i]='B';
if(i==r)break;
for(int j=m;j>=p[i];j--)cdp[j]=max(cdp[j],cdp[j-p[i]]+v[i]);
}
if(l>0){
rep(j,M+1)dp[0][j]=dp[K][j];
}
}
cout<<ans<<endl;
return 0;
}
投稿日時:
最終更新: