[BZOJ2064]分裂

[BZOJ2064]分裂

试题描述

背景: 和久必分,分久必和。。。 题目描述: 中国历史上上分分和和次数非常多。。通读中国历史的WJMZBMR表示毫无压力。 同时经常搞OI的他把这个变成了一个数学模型。 假设中国的国土总和是不变的。 每个国家都可以用他的国土面积代替, 又两种可能,一种是两个国家合并为1个,那么新国家的面积为两者之和。 一种是一个国家分裂为2个,那么2个新国家的面积之和为原国家的面积。 WJMZBMR现在知道了很遥远的过去中国的状态,又知道了中国现在的状态,想知道至少要几次操作(分裂和合并各算一次操作),能让中国从当时状态到达现在的状态。

输入

第一行一个数n1,表示当时的块数,接下来n1个数分别表示各块的面积。 第二行一个数n2,表示现在的块,接下来n2个数分别表示各块的面积。

输出

一行一个数表示最小次数。

输入示例

1 6
3 1 2 3

输出示例

2

数据规模及约定

对于100%的数据,n1,n2<=10,每个数<=50
对于30%的数据,n1,n2<=6,

题解

设 f(S1, S2) 表示 n1 个数中选择了集合 S1 的数,n2 个数中选择了集合 S2 的数。转移的时候我们枚举 S2 的子集 tS,然后找到和 tS 中元素和相同的 S1 的子集 ttS,那么 f(S1, S2) = min{ f(ttS, tS) + f(S1 ^ ttS, S2 ^ tS) },除此之外,还可以直接把 S1 中所有的数合并起来,然后分裂成 S2 中的数,步数即为 S1 中二进制位数 + S2 中二进制位数 - 2。

至于如何找到“和 tS 中元素和相同的 S1 的子集 ttS”,预处理即可,大力搞一个 vector,令 Set[sum] 存储所有元素和等于 sum 的集合。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 15
#define maxs 3010
#define maxsum 510
#define oo 2147483647

int n1, n2, A1[maxn], A2[maxn];
vector <int> Set[maxsum];
int Sum1[maxs], Sum2[maxs], cbin[maxs];

int f[maxs][maxs];
int dp(int S1, int S2) {
	int& ans = f[S1][S2];
	if(ans < oo) return ans;
	if(!cbin[S1] && !cbin[S2]) return ans = 0;
	if(cbin[S1] == 1 && cbin[S2] == 1) return ans = 0;
	ans = cbin[S1] + cbin[S2] - 2;
	for(int tS = S2 - 1 & S2; tS; tS = tS - 1 & S2)
		for(int i = 0; i < Set[Sum2[tS]].size(); i++) if((S1 | Set[Sum2[tS]][i]) == S1)
			ans = min(ans, dp(Set[Sum2[tS]][i], tS) + dp(Set[Sum2[tS]][i] ^ S1, tS ^ S2));
	return ans;
}

int main() {
	n1 = read(); for(int i = 0; i < n1; i++) A1[i] = read();
	n2 = read(); for(int i = 0; i < n2; i++) A2[i] = read();
	
	int all = (1 << n1) - 1;
	for(int S = 0; S <= all; S++) {
		int sum = 0; cbin[S] = 0;
		for(int j = 0; j < n1; j++) if(S >> j & 1) sum += A1[j], cbin[S]++;
		Set[sum].push_back(S); Sum1[S] = sum;
	}
	all = (1 << n2) - 1;
	for(int S = 0; S <= all; S++) {
		cbin[S] = 0;
		for(int j = 0; j < n2; j++) if(S >> j & 1) Sum2[S] += A2[j], cbin[S]++;
	}
	for(int S1 = 0; S1 <= (1 << n1) - 1; S1++)
		for(int S2 = 0; S2 <= all; S2++) f[S1][S2] = oo;
	
	printf("%d\n", dp((1 << n1) - 1, all));
	
	return 0;
}

 

posted @ 2017-06-01 22:09  xjr01  阅读(181)  评论(0编辑  收藏  举报