630 字
3 分钟
BZOJ 2064 分裂 状压DP
2017-10-21

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
对于30%的数据,n1,n2<=6,

题解#

状压DP 首先可以知道用n+m2n+m-2次一定可以实现
所以我们考虑减少次数。
如果原集合能分出来一块与新集合中的一块大小相同。
那么这是一个子问题, 他的答案也是n+m2n+m-2
同样剩下的集合也是如此。 这样我们就把答案减小了2。
然后可以看出我们需要尽可能多的将集合分为几部分。 每部分对应相等。
这可以用状压DP来解决。
可以将新集合中所有值设为相反数, 这样就可以求和为0就可以了。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100];
int S[(1 << 20) + 1], f[(1 << 20) + 1];
int main()
{
    int n, m;
    int cnt = 0;
    scanf ("%d", &n);
    for (cnt = 1; cnt <= n; cnt++)
        scanf ("%d", &a[cnt]);
    scanf ("%d", &m);
    n += m;
    for (; cnt <= n; cnt++)
        scanf ("%d", &a[cnt]), a[cnt] = -a[cnt];
    int N = (1 << n) - 1;
    for (int i = 1; i <= N; i++)
    {
        int j = 0;
        for (j = 1; j <= n; j++)
            if (i & (1 << (j - 1)))
                break;
        S[i] = S[i ^ (1 << (j - 1))] + a[j];
        for (j = 1; j <= n; j++)
            if (i & (1 << (j - 1)))
                f[i] = max(f[i], f[i ^ (1 << (j - 1))] + (!S[i]));
    }
    printf ("%d", n - f[N] * 2);
}
BZOJ 2064 分裂 状压DP
https://www.nekomio.com/posts/114/
作者
NekoMio
发布于
2017-10-21
许可协议
CC BY-NC-SA 4.0