2064: 分裂
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 652 Solved: 401
[ Submit][ Status][ Discuss]
Description
Input
Output
Sample Input
3 1 2 3
Sample Output
数据范围:
对于100%的数据,n1,n2<=10,每个数<=50
对于30%的数据,n1,n2<=6,
HINT
Source
又是一道思路好题... 这道题有用到了一种分治的思想, 将一个问题分成几个小问题.
设原集合为s1, 目标集合为s2. 首先我们知道, 最坏情况下只需要合并n + m - 2次. 只需要先n-1合并成一个大数, 再m-1次分裂即可. 但是显然如果s1有个子集和s2有个子集和相等, 那么那么就可以将s1拆成这个子集和这个子集的补集, 然后再按最坏情况分裂, 这就是n + m - 4. 同理, 如果s1有k个集合对应s2的k个集合, s1的这k个子集分别分裂合并成对应的k个s2子集, 也就成了s2了, 同理只需要n + m - 2 * k次. 那么我们一个贪心思路就是把s1分成尽量多的集合和s2配对.
先姑且不讨论贪心的正确性, 我们讨论一下怎么算最多有多少个集合能够配对. 设f[i]表示为s1, s2分别选取某些数最多能配对多少. 为什么只用i一个二进制数就能表示?只需要把s2的状态放到s1之后就可以了. 那么转移的话就是f[i] = max(f[i], f[i ^ pw[j - 1]]); 就是s1或者s2你拿走一个数之后最多能配对多少. 为什么不用分开枚举s1, s2的子集来转移呢? 因为假设sum[s1] == sum[s2]的话, 假设最大匹配k, 我拿走一个元素一定匹配数-1. 那么也就是说我可以从一个拿走当前一个数的状态转移过来然后+1(前提是sum[s1] == sum[s2], 很明显只要相等一定能再增1对). 这里将s2设为负数只有sum[i]为0就说明当前两集合选择的数sum相等.
那么贪心的正确性呢? 我们发现贪心下的最优状态一定是分成了最多k个子集. 那么每个集合变化成对应配对的集合就需要这个子集大小+对应子集大小-2. 有没有可能更少呢. 现在我们已经分成了k个子问题(即子集), 要保证每个子问题最优即可. 考虑一个子问题. 假设某个子集ss大小为p, 对应s2里的子集gg的大小为q. 最坏情况是需要p+q - 2次操作. 现在我们来证明这也是最优的操作. 我们原来最坏情况下的操作是先合并再分裂. 此时我们可以想象可能最优方法可能是合并分裂合并分裂合并合并夹杂. 那么为了证明此时最坏就是最优, 我们需要简化情况--有没有可能合并分裂合并分裂 等价于 合并合并.... 分裂分裂...即先合并再分裂, 像最坏情况那样呢? 是的. 因为比如说我先把a分裂成a1, a2, 再把a2和b合并. 这样的操作等价于合并a和b, 再分离成a1和a2+b, 操作数相等. 我们现在已经知道操作顺序相邻之间分裂可以与之后的合并互换. 那么一个合并分裂合并合并分裂合并的操作序列一定可以转化成合并合并... 分裂分裂即先合并再分裂的序列. 那么任意操作序列都可以变成最坏情况下的操作模式. 那么我们假设最优操作序列比我们最坏情况优, 肯定是合并次数少(因为要变成的都是gg, 大小一样, 合并次数少也就意味着分裂次数少). 那么为什么合并会少呢? 这就说明合并到某个时候没必要再合并了. 我们设最优情况不必再合并准备开始分裂的时候, 剩余没合并过得数的集合为s3 .因为只能分裂, 这就说明了s3的和等于gg里的某个子集. 但是我们已经保证当前全局最优情况已经分的不能再分, 这个s1的子集ss不可能再有子集s3与gg里的子集相等, 不然还可以再分裂. 所以说最优情况只能一直合并到只剩一个数... 这就是最坏情况的方式啊?? 所以说贪心正确.
整道题的思路: 考虑最坏情况 -> 发现子集对应相等可以减少次数 -> 考虑贪心分成尽可能多的子集 -> 发现贪心正确必须要保证每个子集分裂最优 -> 证明贪心, 发现就是最坏情况的合并次数 -> 贪心正确, 证得最优答案就是n + m - 2 * k (k为最大配对数) -> 状压求解.
#include<stdio.h>
#include<algorithm>
using namespace std;
int n, m, num, tmp, f[1 << 21], sum[1 << 21], pw[21];
int main() {
register int i, j;
pw[0] = 1;
for (i = 1; i <= 20; ++ i) pw[i] = pw[i - 1] << 1;
scanf("%d", &n);
for (i = 1; i <= n; ++ i) scanf("%d", &sum[pw[i - 1]]);
scanf("%d", &m);
for (i = 1; i <= m; ++ i){
scanf("%d", &sum[pw[n + i - 1]]);
sum[pw[n + i - 1]] = - sum[pw[n + i - 1]];
}
num = pw[n + m] - 1;
for (i = 1; i <= num; ++ i) {
tmp = i & -i;
sum[i] = sum[tmp] + sum[i ^ tmp];
for (j = 1; j <= n + m; ++ j)
if (i & pw[j - 1])
f[i] = max(f[i], f[i ^ pw[j - 1]]);
if (!sum[i]) f[i] ++;
}
printf("%d\n", n + m - (f[num] << 1));
return 0;
}