Raucous Rockers:如果这题 \(n \le 5000\) 的话才有下位蓝的难度吧,不然感觉可以降黄了。
暴力 dp
首先根据选的光盘数和当前光盘剩余空间,我们可以设计出一个 dp:定义 \(dp_{i,j,k}\) 表示当前考虑到第 \(i\) 个歌,当前已经用了 \(j\) 个光盘,第 \(j+1\) 个光盘用到了 \(k\) 的体积的最大歌数。
这个背包的转移是显然的,这里不再赘述,时间复杂度 \(O(nmt)\),可以过但是不够优秀。
交换两维 dp
我们考虑一个下标值域很大时而值很小的 dp 的 trick:交换 dp 值与下标两维。
到这题上我们不难发现,dp 数组内的值一定 \(\le n\),所以这就启发我们利用值域小的这个性质设计 dp。那么应该和前面哪一维交换呢?我们考虑贪心思想,当最后选的个数确定的时候,\(i\) 越小越优秀,所以我们可以交换 \(i\)。但是这题还可以交换另一个东西,继续观察,发现当最后选的个数确定的时候,\(j\) 越小越优秀,当 \(j\) 相同时,\(k\) 越小越优秀。
于是,我们一下就可以把 \(j,k\) 和 dp 里存的值进行交换,dp 数组一下就被我们压掉了两维了!比压掉 \(i\) 的一维复杂度更低,是 \(O(n^2)\) 的。
定义 \(dp_{i,j}\) 表示考虑前 \(i\) 首歌,选其中 \(j\) 首歌的最小代价,里面存的是一个 pair
,于是可以写出如下转移:
其中 \(upd\) 表示将 \(dp_{i-1,j-1}\) 再加入一个长度为 \(a_i\) 的歌的最小代价。
注意特判 \(a_i > t\) 的情况,直接将当前使用的光盘数量设为极大值。
统计答案的时候输出满足使用光盘数 \(\le m\) 的最大的 \(j\) 即可。
代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=1005;
int n,t,m,a[N],ans=0,sid;
pi dp[N][N];
pi upd(pi ori,int w)
{int x=ori.fi,y=ori.se+w;if(y>t)x++,y=w;if(w>t)x=0x3f3f3f3f;return {x,y};
}
void solve(int id)
{cin>>n>>t>>m;ans=0;for(int i=1;i<=n;i++){char x;cin>>a[i];if(i<n)cin>>x;}memset(dp,0x3f,sizeof(dp));dp[0][0]={0,0};for(int i=1;i<=n;i++){for(int j=0;j<=i;j++){dp[i][j]=dp[i-1][j];if(j)dp[i][j]=min(dp[i][j],upd(dp[i-1][j-1],a[i]));if(dp[i][j].fi+1<=m)ans=max(ans,j);}}cout<<ans<<'\n';if(id<sid)cout<<'\n';
}
int main()
{//freopen("sample.in","r",stdin);//freopen("sample.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>sid;for(int i=1;i<=sid;i++)solve(i);return 0;
}