[BZOJ] 2064: 分裂
注意到\(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]\not=sum[y]\),枚举删去\(x\)或\(y\)的一个元素
\[f[x][y]=max\{f[x\oplus i][y],f[x][y\oplus j]\}
\]
若\(sum[x]=sum[y]\),情况看起来复杂了
为了转移,我们似乎要再枚举子集,复杂度不能接受
但是这样想,既然\(sum[x]=sum[y]\),那么\(x\)和\(y\)至少配成了一对,若在其中删去一个元素,一定会减少一对
因此
\[f[x][y]=1+max\{f[x\oplus i][y],f[x][y\oplus 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[x\oplus 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('\n')
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;
}
本文来自博客园,作者:GhostCai,转载请注明原文链接:https://www.cnblogs.com/ghostcai/p/9802403.html