题解:P3878 [TJOI2010] 分金币
背景
貌似写这篇文章时题解只有一篇是正确的。而这唯一的一篇做法又比较麻烦,所以给出了思路简单实现容易的正确做法。
题意
给你一个长度为
思路
数据范围
我们先考虑前一半的数字,状压考虑每个数选不选,统计出选和不选的数的数量差 set 记录前一半数字中,对于每种数量差可能的总和差。
然后考虑后一半的数字,同样算出选和不选的数量差
这样做复杂度就是
实现
#include<bits/stdc++.h>
using namespace std;
const int N = 40;
typedef long long LL;
int a[N],n;
set<LL> s[2 * N];
LL calc(LL x,LL y)
{
auto t = s[x].lower_bound(y);
if(t != s[x].end()) return (*t) - y;
return 1e18;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin>>t;
while(t --)
{
cin>>n;
for(int i = 1; i <= n; i ++) cin>>a[i];
for(int i = 0; i <= 2 * n; i ++) s[i].clear(),s[i].insert(1e18),s[i].insert(-1e18);
LL res = 1e18,m = n / 2,k = n - n / 2;
for(int i = 0; i < (1 << m); i ++)
{
LL cnt = n,sum = 0;
for(int j = 1; j <= m; j ++)
if((i >> j - 1) & 1) cnt ++,sum += a[j];
else cnt --,sum -= a[j];
s[cnt].insert(sum);
}
for(int i = 0; i < (1 << k); i ++)
{
LL cnt = n,sum = 0;
for(int j = 1; j <= k; j ++)
if((i >> j - 1) & 1) cnt --,sum -= a[m + j];
else cnt ++,sum += a[m + j];
if(n & 1)
{
if(cnt) res = min(res,(*s[cnt - 1].lower_bound(sum)) - sum);
res = min(res,(*s[cnt + 1].lower_bound(sum)) - sum);
if(cnt) res = min(res,sum - (*-- s[cnt - 1].upper_bound(sum)));
res = min(res,sum - (*-- s[cnt + 1].upper_bound(sum)));
}
else
{
res = min(res,(*s[cnt].lower_bound(sum)) - sum);
res = min(res,sum - (*-- s[cnt].upper_bound(sum)));
}
}
cout<<res<<'\n';
}
return 0;
}