题意:
有一个n个数字的可重集合,给出
2n
个子集的和,最多10000种不同的值。问排序后字典序最小原集合。
n<=60
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#define N 11000
#define LL long long
using namespace std;
struct node{LL d,f;}a[N],b[N],c[N],r[N];
LL ans[N];
int n,m,num;
LL gcd(LL a,LL b)
{
if(b==0) return a;
return gcd(b,a%b);
}
int find(LL t)
{
int l=1,r=n;
while(l<=r)
{
int mid=(l+r)/2;
if(a[mid].d==t) return mid;
if(a[mid].d>t) r=mid-1;
else l=mid+1;
}
return -1;
}
bool check(LL t)
{
for(int i=1;i<=n;i++) b[i]=c[i]=a[i],c[i].f=0;
if(t>0)
{
for(int i=n;i>=1;i--)
{
if(b[i].f==0) continue;
int k=find(b[i].d-t);
if(k==-1 || b[i].f>b[k].f) return 0;
b[k].f-=b[i].f;c[k].f+=b[i].f;
}
}
else
{
for(int i=1;i<=n;i++)
{
if(b[i].f==0) continue;
int k=find(b[i].d-t);
if(k==-1 || b[i].f>b[k].f) return 0;
b[k].f-=b[i].f;c[k].f+=b[i].f;
}
}
for(int i=1;i<=n;i++) if(c[i].d==0 && c[i].f==0) return 0;
return 1;
}
int main()
{
int z,zu=0;scanf("%d",&z);
while(z--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i].d);
for(int i=1;i<=n;i++) scanf("%lld",&a[i].f);
LL g=a[1].f;
for(int i=1;i<=n;i++) g=gcd(g,a[i].f);
int tmp=0,tn=n;
while(g%2==0) g/=2,tmp++;
for(int i=1;i<=n;i++) a[i].f/=1ll<<tmp;
for(int i=1;i<=n;i++) r[i]=a[i];
num=0;
while(n>1)
{
LL x=a[2].d-a[1].d;
check(x);
m=0;
for(int i=1;i<=n;i++) if(c[i].f) a[++m]=c[i];
n=m;
ans[++num]=x;
}
sort(ans+1,ans+num+1);
n=tn;
for(int i=1;i<=n;i++) a[i]=r[i];
for(int i=num;i>=1;i--)
{
if(check(-ans[i])==0) check(ans[i]);
else ans[i]=-ans[i];
m=0;
for(int i=1;i<=n;i++) if(c[i].f) a[++m]=c[i];
n=m;
}
while(tmp) ans[++num]=0,tmp--;
sort(ans+1,ans+num+1);
printf("Case #%d: ",++zu);
for(int i=1;i<=num;i++) printf("%lld ",ans[i]);
printf("\n");
}
return 0;
}
题解:
首先,最小的负值出现次数是
2p
,代表有p个0。
证明:
假设把所有0拿走,如果拿走了k个0,那所有值的出现次数都要除
2k
。在没有0的情况下,构成最小值的方法只有一种,就是所有负数的和,故最小负值出现次数就是
2k
先把所有0拿走
设最小负值和次小负值的差为d。最小负值一定是所有负数的和,次小负值要么是最小负值减最大负数,要么是最小负值加最小正数,说明d和-d一定存在一个。
然后我就不知道该拿哪一个,看了一眼题解
题解说,不管拿哪一个,本质上是没有区别的,意思就是最终拿出来的所有数的绝对值的集合是一样的。
脑补一下确实是这样的,考虑拿走一个数d之后,要把所有值分成两个集合,包含d的集合里所有的值减d后和不包含d的集合相等。
对于两个值x,y,如果|x-y|=d,就会在这两个值之间连边,然后看d的正负从小到大或从大到小扫一遍。
但是注意,d和-d的区别只是所有值都从去小的那一边还是都去大的那一边。
也就是说,选择d和-d得到的是两个平移后相等的集合,那么下一步的选择d’的绝对值是不会改变的。
于是就可以把所有数的绝对值拿出来,排序后从大到小贪心。合法的情况下做出来的值里一定会包含0。先假设当前数是负的,然后从小到大扫一遍,判合不合法就好了。