bzoj2064 分裂

2064: 分裂

Time Limit: 10 Sec  Memory Limit: 64 MB
Submit: 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

Sample Output

2
数据范围:
对于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;
}

 

posted @ 2018-02-21 00:02  zbtrs  阅读(200)  评论(0编辑  收藏  举报