注意到(n)很小,应该是状压DP
记原集合为(S),目标集合为(T),如果我们能把(S)分成(x)个不相交的非空子集,且这(x)个子集能和(T)中的一些不相交非空子集的和相等,那么最终答案就是(n+m-2x),其中(n=|S|,m=|T|)
因此我们要最大化(x),这就是DP的目标了
设(f[x][y])表示(S)的子集(x)和(T)的子集(y),(sum)相等的最多能配几对
若(sum[x] ot=sum[y]),枚举删去(x)或(y)的一个元素
[f[x][y]=max{f[xoplus i][y],f[x][yoplus j]}
]
若(sum[x]=sum[y]),情况看起来复杂了
为了转移,我们似乎要再枚举子集,复杂度不能接受
但是这样想,既然(sum[x]=sum[y]),那么(x)和(y)至少配成了一对,若在其中删去一个元素,一定会减少一对
因此
[f[x][y]=1+max{f[xoplus i][y],f[x][yoplus j]}
]
这样省去了枚举子集,复杂度有保障
最终复杂度(O((n+m)2^{n+m}))
骚操作:如何统计(sum[x])?
以前一直写
for(int S=0;S<(1<<n);S++)
for(int i=0;i<n;i++)
if(S&(1<<i))sum[S]+=a[i];
这样做是(O(n2^n))的,实际上,完全可以省去一个(O(n)) (虽然(n)很小..)
类似统计二进制中1的个数一样,可以复用(S)的子集,方法就是用lowbit
也就是(sum[x]=sum[xoplus lowbit(x)]+sum[lowbit(x)])
边界是(sum[1<<i]=a[i+1])
这样是(O(2^n))的
#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
inline int rd(){
int ret=0,f=1;char c;
while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
while(isdigit(c))ret=ret*10+c-'0',c=getchar();
return ret*f;
}
#define space putchar(' ')
#define nextline putchar('
')
void _(int x){if(!x)return;_(x/10);putchar('0'+x%10);}
void out(int x){if(!x)putchar('0');_(x);}
const int MAXN = 10;
inline void upmax(int &x,int y){x=max(x,y);}
int a[MAXN],b[MAXN];
int f[1<<MAXN][1<<MAXN];
int sum1[1<<MAXN],sum2[1<<MAXN];
int n,m;
int main(){
n=rd();
for(int i=1;i<=n;i++)sum1[1<<(i-1)]=a[i]=rd();
m=rd();
for(int i=1;i<=m;i++)sum2[1<<(i-1)]=b[i]=rd();
for(int i=1;i<(1<<n);i++){
sum1[i]=sum1[i^(i&-i)]+sum1[i&-i];
}
for(int i=1;i<(1<<m);i++){
sum2[i]=sum2[i^(i&-i)]+sum2[i&-i];
}
for(int s=1;s<(1<<n);s++){
for(int t=1;t<(1<<m);t++){
for(int i=0;i<n;i++){
if(s&(1<<i)) upmax(f[s][t],f[s^(1<<i)][t]);
}
for(int i=0;i<m;i++){
if(t&(1<<i)) upmax(f[s][t],f[s][t^(1<<i)]);
}
if(sum1[s]==sum2[t])f[s][t]++;
}
}
cout<<n+m-2*f[(1<<n)-1][(1<<m)-1];
return 0;
}