bzoj2064 分裂
2064: 分裂
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 708 Solved: 435
[Submit][Status][Discuss]
Description
背景: 和久必分,分久必和。。。 题目描述: 中国历史上上分分和和次数非常多。。通读中国历史的WJMZBMR表示毫无压力。 同时经常搞OI的他把这个变成了一个数学模型。 假设中国的国土总和是不变的。 每个国家都可以用他的国土面积代替, 又两种可能,一种是两个国家合并为1个,那么新国家的面积为两者之和。 一种是一个国家分裂为2个,那么2个新国家的面积之和为原国家的面积。 WJMZBMR现在知道了很遥远的过去中国的状态,又知道了中国现在的状态,想知道至少要几次操作(分裂和合并各算一次操作),能让中国从当时状态到达现在的状态。
Input
第一行一个数n1,表示当时的块数,接下来n1个数分别表示各块的面积。 第二行一个数n2,表示现在的块,接下来n2个数分别表示各块的面积。
Output
一行一个数表示最小次数。
Sample Input
1 6
3 1 2 3
3 1 2 3
Sample Output
2
数据范围:
对于100%的数据,n1,n2<=10,每个数<=50
数据范围:
对于100%的数据,n1,n2<=10,每个数<=50
分析:真是神题一道啊!
这道题的解法和ioi2017 古书那道题有异曲同工之妙.先分析出一个理论上界:n1 + n2 - 2,怎么分析出来的呢?先把以前的面积都合成一块,然后一个一个分配到现在.如果能让这个上界尽可能地小,那么就是答案了.
如何让上界更小呢?对于有的部分,我们可以不把它合并到整个块中,它可以单独处理.什么样部分满足这一条件呢?现在集合有子集的面积和那一部分相等.举个例子:
2 1 3 4; 2 2 2 4. 左右两个端点的数就是这样的部分,中间那一段也是一部分,因为它们在右边都有对应的子集的和相等,并且不相交.处理出最多能分出多少个这样的部分cnt,答案就是n1 + n2 - 2*cnt.这是最小的上界,也是下界.
如何做?首先要统计两个集合中每个子集的子集的和,还要统计这两个集合的子集能分成多少个部分. 对于第一个问题,采用递推的方式:sum[i] = sum[i ^ lowbit(i)] + sum[lowbit(i)]. 实际上就是把最右边的1统计进入答案. 对于第二个问题,枚举状态S.枚举状态S其实就相当于每次新加1个1进来并且把若干个1变成0,若干个0变成1,可以任选一个已经存在的1,将它变成0,并从这个状态转移过来.最后判断和是否相等.如果相等,则说明新加进来的一个数就又可以分为一部分. 答案++. 这样避免了每次枚举子集来转移.
两个问题的状压dp转移都非常巧妙,值得一学! 确定理论上界,并且讨论可以优化的情况来确定答案也是一种方法!
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int n,m,sum1[(1 << 10) + 1],sum2[(1 << 10) + 1],maxn,maxm,f[(1 << 10) + 1][(1 << 10) + 1]; int main() { scanf("%d",&n); for (int i = 1; i <= n; i++) scanf("%d",&sum1[(1<<(i - 1))]); scanf("%d",&m); for (int i = 1; i <= m; i++) scanf("%d",&sum2[(1 <<(i - 1))]); maxn = (1 << n) - 1; maxm = (1 << m) - 1; for (int i = 1; i <= maxn; i++) sum1[i] = sum1[i ^ (i & (-i))] + sum1[i & (-i)]; for (int i = 1; i <= maxm; i++) sum2[i] = sum2[i ^ (i & (-i))] + sum2[i & (-i)]; for (int i = 1; i <= maxn; i++) for (int j = 1; j <= maxm; j++) { for (int k = 0; k < max(n,m); k++) { if ((1 << k) & i) f[i][j] = max(f[i ^ (1 << k)][j],f[i][j]); if ((1 << k) & j) f[i][j] = max(f[i][j ^ (1 << k)],f[i][j]); } if (sum1[i] == sum2[j]) f[i][j]++; } printf("%d\n",n + m - 2 * f[maxn][maxm]); return 0; }