bzoj2064[和谐社会模拟赛]分裂
题意:给定一个初始集合和目标集合,有两种操作:1.合并集合中的两个元素,新元素为两个元素之和 2.分裂集合中的一个元素,得到的两个新元素之和等于原先的元素。要求用最小步数使初始集合变为目标集合,求最小步数。
记集合S的元素之和为sum(S)
首先,如果初始集合的子集S1和目标集合的子集S2满足sum(S1)==sum(S2),那么我们就可以把S1合并为一个元素再拆分为S2,然后再处理两个集合剩下的部分。
我们得到了一个很显然的状态转移方程。记f[S1][S2]为使用初始集合的S1子集得到目标集合的S2子集的最小花费(如果状态合法,必须满足sum(S1)==sum(S2),但并不一定是将S1全部合并为一个元素再拆成S2,因为S1的某个子集还可以与S2的某个子集的和相等,从而用S1的几个子集分别得到S2的几个子集)。转移的时候,如果直接暴力枚举S1和S2的每一个子集判断能否匹配,复杂度会达到(2^n)^4,30分。
这时有一个显然的优化,就是只考虑合法的状态,对于非法的状态(S1的和不等于S2的状态)不进行求解。我的做法是预处理出所有合法状态,然后仅在合法状态之间进行转移,复杂度为O(m^2),m为合法状态数。如果出题人愿意造数据的话,这个做法是可以卡掉的,比如初始状态和末状态都是10个1,那么总的合法状态数为C(10,1)^2+C(10,2)^2+C(10,3)^2+….+C(10,10)^2,达到了184755,肯定会T。但是我在bzoj上assert了一发,发现最多的合法状态数大于5000小于8000,那么平方的复杂度就过掉了。
不会被卡的做法是:我们转移的方式是将初始集合分成几个子集对应到目标集合的几个子集。假如我们将初始集合分成了x个子集,那么最后的花费就是初始集合元素个数+目标集合元素个数-2*x(举几个例子就能发现这个规律)。由于元素个数已经确定,我们的最优化目标转化为将初始集合分成尽量多的子集与目标集合的同样数目的子集一一对应(元素之和相等)。
记f[S1][S2]为从初始集合的S1子集和目标集合的S2子集中选取元素,最多能组成几组元素和相等的集合。(不一定使用S1和S2中的所有元素)。
转移时,我们分两种情况讨论,一种是sum(S1)!=sum(S2),这时一定无法用上所有的元素,那么只要枚举没有使用的一个元素S1中的i或S2中的j,f[S1][S2]=max(f[S1^i][S2],f[S1][S2^j])
一种是sum(S1)==sum(S2),这时一定可以用上所有的元素,那么我们是否需要枚举S1,S2的所有子集?看似只能通过这个方法使得状态从前面转移过来,那么复杂度上界又变成了(2^n)^4,30分。不过,再仔细想想。如果我们在最优解中拿掉一个元素,那么能够配对的集合对数一定会减1.反过来,f[S1][S2]的最优解一定可以通过一个在S1或S2中拿掉一个元素的状态增加一个配对的集合得到。(由于sum(S1)==sum(S2),而且任何一个状态中已经配对的元素都满足两边总和相等,所以拿掉一个元素后的状态在增添一个元素后一定可以增加一组配对的集合。)这个用枚举元素代替枚举子集的思路很巧妙。
仍然枚举S1,S2中的元素i,j,那么f[S1][S2]= max(f[S1^i][S2],f[S1][S2^j])+1。
#include<cstdio> #include<cstring> #include<cassert> const int inf=0x3f3f3f3f; inline int lowbit(int x){ return x&(-x); } inline int min(int a,int b){ return a<b?a:b; } int a[15],b[15]; int suma[1024],sumb[1024]; int f[1024][1024]; int g[1024]; const int maxn=10005; int u[maxn],v[maxn]; int main(){ int n,m; scanf("%d",&n); for(int i=0;i<n;++i){ scanf("%d",a+i); suma[1<<i]=a[i]; } scanf("%d",&m); for(int i=0;i<m;++i){ scanf("%d",b+i); sumb[1<<i]=b[i]; } int lim1=1<<n,lim2=1<<m; for(int i=1;i<lim1;++i){ suma[i]=suma[i^lowbit(i)]+suma[lowbit(i)]; } for(int i=1;i<lim2;++i){ sumb[i]=sumb[i^lowbit(i)]+sumb[lowbit(i)]; } for(int i=1;i<1024;++i)g[i]=g[i>>1]+(i&1); int cnt=0; memset(f,0x3f,sizeof(f)); for(int i=1;i<lim1;++i){ for(int j=1;j<lim2;++j){ if(suma[i]==sumb[j]){ u[++cnt]=i; v[cnt]=j; f[i][j]=g[i]+g[j]-2; } } } for(int i=1;i<=cnt;++i){ for(int j=i+1;j<=cnt;++j){ if((u[i]&u[j])||(v[i]&v[j])){ continue; } f[u[i]|u[j]][v[i]|v[j]]=min(f[u[i]|u[j]][v[i]|v[j]],f[u[i]][v[i]]+f[u[j]][v[j]]); } } assert(cnt<=8000); printf("%d\n",f[lim1-1][lim2-1]); return 0; }
#include<cstdio> #include<cstring> const int inf=0x3f3f3f3f; inline int lowbit(int x){ return x&(-x); } inline int max(int a,int b){ return a>b?a:b; } int a[15],b[15]; int suma[1024],sumb[1024]; bool e[1024][1024]; int f[1024][1024]; int g[1024]; int main(){ int n,m; scanf("%d",&n); for(int i=0;i<n;++i){ scanf("%d",a+i); suma[1<<i]=a[i]; } scanf("%d",&m); for(int i=0;i<m;++i){ scanf("%d",b+i); sumb[1<<i]=b[i]; } int lim1=1<<n,lim2=1<<m; for(int i=1;i<lim1;++i){ suma[i]=suma[i^lowbit(i)]+suma[lowbit(i)]; } for(int i=1;i<lim2;++i){ sumb[i]=sumb[i^lowbit(i)]+sumb[lowbit(i)]; } for(int i=1;i<lim1;++i){ for(int j=1;j<lim2;++j){ for(int k=0;k<n;++k){ if(i&(1<<k))f[i][j]=max(f[i][j],f[i^(1<<k)][j]); } for(int k=0;k<m;++k){ if(j&(1<<k))f[i][j]=max(f[i][j],f[i][j^(1<<k)]); } if(suma[i]==sumb[j])f[i][j]++; } } printf("%d\n",n+m-2*f[lim1-1][lim2-1]); return 0; }