【FZUOJ 2178】礼物分配
找在分配数目差不超过1的情况下 |sumv-sumw|的最小值
一般思路是找出所有情况然后做差 很明显2^30会超
会想到类似哈希的思路 分半 先找前一半物品分配给两人的所有方案 然后在后一半找满足分配完两人的数目差不超1的所有方案 用后一半找前一半匹配中|sumv-sumw|的最小值 所有最小值中最小值即为答案 用后一半找前一半匹配时可用二分
代码如下:
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
int v[33],w[33],n;
vector <int> vc[16];//存放前一半分配给第一人i个物品后的|sumv-sumw|
int Binary(int pos,int x)//在前一半找后一半分配差x的匹配值 越接近x 做差越小
{
int mid,l = 0, r = vc[pos].size()-1, ans = -1;
while(l <= r)
{
mid = (l+r)>>1;
if(vc[pos][mid] == -x) return 0;
if(vc[pos][mid] > -x)
{
ans = mid;
r = mid-1;
}
else l = mid+1;
}
if(ans == -1) return abs(vc[pos][vc[pos].size()-1]+x);
if(!ans) return abs(vc[pos][0]+x);
else return min(abs(vc[pos][ans]+x),abs(vc[pos][ans-1]+x));
}
int main()
{
int t,i,j,tot,m1,m2,cnt,ans1,ans2,mn;
scanf("%d",&t);
while(t--)
{
memset(vc,0,sizeof(vc));
scanf("%d",&n);
for(i = 0; i < n; ++i) scanf("%d",&v[i]);
for(i = 0; i < n; ++i) scanf("%d",&w[i]);
if(n == 1)
{
printf("%d\n",min(v[0],w[0]));
continue;
}
m2 = n/2;
m1 = (n+1)/2;
tot = 1<<m1;
for(i = 0; i < tot; ++i)//找出前一半分配的所有情况
{
cnt = ans1 = ans2 = 0;
for(j = 0; j < m1; ++j)
{
if((1<<j)&i)
{
ans1 += v[j];
cnt++;
}else ans2 += w[j];
}
vc[cnt].push_back(ans1-ans2);
}
for(i = 0; i < m1; ++i) sort(vc[i].begin(),vc[i].end());
mn = INF;
tot = 1<<m2;
for(i = 0; i < tot; ++i)
{
cnt = ans1 = ans2 = 0;
for(j = m1; j < n; ++j)
{
if((1<<(j-m1))&i)
{
ans1 += v[j];
cnt++;
}else ans2 += w[j];
}
if(n&1)//总数为奇数时 两人物品差1 二分两次
{
mn = min(mn,Binary(m2-cnt,ans1-ans2));
}
mn = min(mn,Binary(m1-cnt,ans1-ans2));
if(!mn) break;
}
printf("%d\n",mn);
}
return 0;
}