【FZU2178】礼物分配
题意
在双胞胎兄弟Eric与R.W的生日会上,他们共收到了N个礼物,生日过后他们决定分配这N个礼物(numv+numw=N)。对于每个礼物他们俩有着各自心中的价值vi和wi,他们要求各自分到的礼物数目|numv-numw|<=1,并且各自所衡量的礼物价值的差值|sumv-sumw|尽可能小,现在他们想知道最小的差值是多少。
分析
这是中途相遇法的模板题
每个礼物要么属于Eric,要么属于R.W,所以如果暴力的话是2^30,显然会超。
使用中途相遇法可以将复杂度降到2^15左右,很是神奇。
1. 先将N个礼物分成两份,第一份有n/2个礼物,第二份有n-n/2个礼物。
2. 然后枚举第一份中有哪些属于Eric,哪些属于R.W。cnt来记录第一份中Eric的礼物数目,sum1是第一份中Eric的礼物价值和,sum2是R.W的礼物价值和。然后用一个vector,把每个sum1-sum2都加到下标为cnt的vector中。
3. 用类似的方法,枚举第二份中哪些属于Eric,哪些属于R.W。sum1,sum2,cnt的含义相同。然后在下标为n-n/2-cnt的vector中找和这个sum1-sum2相加最小的值,然后判断是否要更新ans。
就是这样,用这个题来学中途相遇法了。。。步骤3中的小细节还是比较神奇的。
下面是代码
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <vector> 6 #include <map> 7 8 using namespace std; 9 typedef long long LL; 10 11 const int maxn=35; 12 const int INF=2147000000; 13 int T,n; 14 int v[maxn],w[maxn]; 15 vector<int>V[maxn]; 16 17 int main(){ 18 scanf("%d",&T); 19 for(int t=1;t<=T;t++){ 20 scanf("%d",&n); 21 for(int i=0;i<=n;i++)V[i].clear(); 22 for(int i=1;i<=n;i++) 23 scanf("%d",&v[i]); 24 for(int i=1;i<=n;i++) 25 scanf("%d",&w[i]); 26 int n1,n2; 27 n1=n/2,n2=n-n1; 28 int cnt; 29 LL sum1,sum2; 30 for(int i=0;i<(1<<n1);i++){ 31 cnt=0,sum1=0,sum2=0; 32 for(int j=0;j<n1;j++){ 33 if(i&(1<<j)){ 34 cnt++; 35 sum1+=v[j+1]; 36 }else 37 sum2+=w[j+1]; 38 } 39 V[cnt].push_back(sum1-sum2); 40 } 41 42 for(int i=0;i<=n1;i++){ 43 sort(V[i].begin(),V[i].end()); 44 V[i].erase(unique(V[i].begin(),V[i].end()),V[i].end()); 45 } 46 47 48 int ans=INF; 49 for(int i=0;i<(1<<n2);i++){ 50 cnt=0,sum1=0,sum2=0; 51 for(int j=0;j<n2;j++){ 52 if(i&(1<<j)){ 53 cnt++; 54 sum1+=v[n1+j+1]; 55 }else{ 56 sum2+=w[n1+j+1]; 57 } 58 } 59 int cnt1,SUM; 60 cnt1=n2-cnt,SUM=sum1-sum2; 61 vector<int>::iterator it; 62 it=lower_bound(V[cnt1].begin(),V[cnt1].end(),-SUM); 63 if(it!=V[cnt1].end()&&abs(*it+SUM)<ans){ 64 ans=abs(*it+SUM); 65 } 66 if(it!=V[cnt1].begin()){ 67 it--; 68 if(abs(*it+SUM)<ans) 69 ans=abs(*it+SUM); 70 } 71 } 72 printf("%d\n",ans); 73 } 74 return 0; 75 }