bzoj2064分裂 关于一类状压问题的总结
来源:互联网 发布:eraser软件 编辑:程序博客网 时间:2024/05/16 05:23
2064: 分裂
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 660 Solved: 406
[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
对于30%的数据,n1,n2<=6,
数据范围:
对于100%的数据,n1,n2<=10,每个数<=50
对于30%的数据,n1,n2<=6,
HINT
Source
和谐社会模拟赛
好题好题。可以当作状态压缩的一种DP模板。
1. 最终状态是2^n-1
2. 各个子集之间的答案互不影响,且对于每种状态S,f(S)=f(T)+f(S-T),其中T为S子集
3. 一个合法状态S的最差答案=S包含1的个数-a(或+a)
4. 目标最小化(最大化)
在这个时候,划分子集划分得越多越好。所以我们把问题转化为把S分成尽量多的合法状态。于是我们设f[S]为将S划分成最多的子集个数。方程有f[S]=max{f[T]+f[S-T],f[S]}(注意S,T,S-T都必须是合法状态),其中T为S子集,[]为布尔表达式。最后的答案就是n-a*f[2^n-1]
这样子初步设计出了O(3^n)的算法
特别地,若f[2^i]=0,也就是若S只由单个1组成时不是合法状态吗,并且去掉一个1之后也不是合法状态,我们可以令f[S]=max{f[S-2^i]}+[S为合法状态],其中S的第i位是1,也就是把S划分成单个1和其余部分。因为此时S-2^i是一个最优子结构。因为任意一个其余的子集一定包含在这些子集之中。这样的算法可以优化到O(n2^n)(此方法慎用)
套到本题,我们把新旧设计在从一个状态中。我们定义一个状态的sum值为新的面积减去旧的面积,那么当sum是0的时候,显然是一个合法的变化状态,假设这个时候新旧加起来是n个数,我们把新的并起来,在逐一拆回旧的,那么一共经历了n-2次变换。这个和上述模板不谋而合。
然后就可以开心的状压了。
相同类型的题目还有bzoj3900,可以去试看看
#include<iostream>#include<cstdlib>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>#include<vector>using namespace std;const int N = 3000000;int read(){ char ch = getchar(); int x = 0, f = 1; while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();} return x * f;}int n, m, bin[30], sum[N], f[N]; int main(){ bin[0] = 1; for(int i = 1;i <= 25; ++i) bin[i] = bin[i - 1] << 1; n = read(); for(int i = 0;i < n; ++i) sum[bin[i]] = read(); m = read(); for(int i = 0;i < m; ++i) sum[bin[i + n]] = -read(); n += m; for(int i = 1;i < bin[n]; ++i) { int t = i & (-i); sum[i] = sum[t] + sum[i ^ t]; for(int j = 0; j < n; ++j) if(i & bin[j]) f[i] = max(f[i], f[i ^ bin[j]]); if(!sum[i]) ++f[i]; } printf("%d\n", n - (f[bin[n] - 1] << 1)); return 0;}
阅读全文