n1和n2都小于等于10,考虑状态压缩动态规划
预处理:
d[ i ][ j ]表示开始状态的子集i,变成结束状态的子集j所需要的步数。(这里需要i的面积和与j的面积和相等,所需要的步数是暴力合并i和暴力拆分j的步数)
DP:
F[ i ][ j ]表示当前剩余状态开始状态的i,和结束状态的j。
枚举i的子集p , j ^ (1 << (n2-1))的子集q // q是全集j的补集的子集
主动递推:
F[ i ^ p ][ j | q ] = min{ F[ i ][ j ] + d[ p ][ q ] };
注意:
1、动态规划的无后效性,即i从大到小枚举,j从小到大枚举。
2、注意枚举子集的方法
3、要判断i + j的面积是否为全集的面积,否则会TLE
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 2049
using namespace std;
int x[N],y[N],n1,n2;
int d[N][N],F[N][N],S,T;
int lowbit(int x) { return (x & (-x)); }
int c(int x) {
int t = 0;
while (x) { t++; x -= lowbit(x); }
return t;
}
int sumx(int i) {
int t = 0 , tmp = 0;
while (i) {
t = lowbit(i);
tmp += x[t];
i -= t;
}
return tmp;
}
int sumy(int i) {
int t = 0 , tmp = 0;
while (i) {
t = lowbit(i);
tmp += y[t];
i -= t;
}
return tmp;
}
void Pre() {
for (int i=1;i<=(1<<n1)-1;i++)
for (int j=1;j<=(1<<n2)-1;j++)
if (sumx(i) == sumy(j)){
d[i][j] = c(i) - 1 + c(j) - 1;
//printf("%d %d %d\n",i,j,d[i][j]);
}
}
void DP() {
int dhr = (1 << n2) - 1;
for (int i=(1<<n1)-1;i>=0;i--)
for (int j=0;j<=(1<<n2)-1;j++)
if (sumx(i) + sumy(j) == sumx(S))
for (int p=i;p;p=((p-1)&i))
for (int q=j^dhr;q;q=((q-1)&(j^dhr))) {
F[i^p][j|q] = min(F[i][j] + d[p][q] , F[i^p][j|q]);
}
}
int main()
{
memset(F,127/3,sizeof(F));
memset(d,127/3,sizeof(d));
scanf("%d",&n1);
for (int i=1;i<=n1;i++) scanf("%d",&x[1 << (i-1)]);
scanf("%d",&n2);
for (int i=1;i<=n2;i++) scanf("%d",&y[1 << (i-1)]);
S = (1<<n1) - 1; T = (1<<n2) - 1;
F[S][0] = 0;
Pre(); DP();
printf("%d\n",F[0][T]);
return 0;
}