题意:
多组输入,给定a,b两个数组,长度分别为n,m。每个元素有两个值:wi,vi,要求从两个数组中分别选出一个子数组,使得两个子数组的wi和相同,并使总的vi之和最大。
数据范围:n,m<=1e3,wi<=1e3,vi<=1e9,(sum(n+m)<=1e4)
解法:
很容易想到分别对两个数组进行背包,求出每种可能重量和的价值最大值,然后遍历两个dp数组求出价值和的最大值,复杂度为(n^2*wi),但是这样一组样例就有1e9,5组就有5e9,稳TLE了。
首先想到将两个背包合并为一个,即对b数组的wi取反加入到a数组中,答案就是对a数组做背包后dp[0]的值。(由于数组下标不能为负,需要对每个状态加上一个base值,使所有状态对应的下标为正)。
合并为一个背包后,可以发现最后需要的答案就是一个dp[0](为方便描述暂且不加上base值),如果对所有可能的重量的状态进行转移,会浪费掉大量对答案不一定有贡献的时间,那么如何避免这种情况呢?应该采用对合并后的a数组进行随机化的方法,基于一个随机化的数组,绝对值很大的状态再转移回dp[0]的概率是很低的,因此可以不考虑绝对值很大的状态,即减小dp数组的范围,这样答案也是有很大的几率是正确的。
算一下时间复杂度,评测机能跑5s,就是5e8的计算量,由于总和有限制,对于n=1000,m=1000这种数据最多有5组,因此一组数据计算量是1e8,背包要做n+m次,每次计算量为5e4(但是这样可能会wa,要开大一点开1e5,评测机能3s稳过)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1e15;
const int maxn=2e3+5;
const int maxdp=1e5+5;
const int base=5e4;
const int lim=1e5;
pair<int,int> pi[maxn];
void debug(ll *a,int l,int r){
for(int i=l;i<=r;i++){
printf("%d ",a[i]);
}
puts("");
}
ll w[maxn],v[maxn];
ll temp[maxdp];
ll dp[maxdp];//dp[i]:i-base重量的最大价值
void init(){
fill(dp,dp+maxdp,-INF);
fill(temp,temp+maxdp,-INF);
dp[base]=0;
temp[base]=0;
}
int main () {
srand(time(0));
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=n+m;i++){
scanf("%d%d",&pi[i].first,&pi[i].second);
if(i>n){
pi[i].first=-pi[i].first;
}
}
random_shuffle(pi+1,pi+1+n+m);
for(int i=1;i<=n+m;i++){
w[i]=pi[i].first;
v[i]=pi[i].second;
}
for(int i=1;i<=n+m;i++){
for(int j=min(lim,(int)lim+(int)w[i]);j-w[i]>=0;j--){
if(dp[j-w[i]]!=-INF){
temp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
for(int j=0;j<=maxdp-1;j++){
dp[j]=temp[j];
}
}
printf("%lld
",dp[base]);
}
}