【ybtoj】【状压dp】最优组队--状压必看实用技巧【子集枚举】

题意

题目描述

有 n 个人打算分成 n 个小组,对于这 n 个人的任意一个组合,都有一个被称为“和谐度”的东西。现在,他们想知道,如何分组可以使和谐度总和最大。每个人必须属于某个分组,可以一个人一组。

输入格式

第  行为 ,表示有  个人。
接下来2n-1行,按照  进制给出每个分组的和谐度。(比如接下来第 5 行,也就是总共第 6 行,2 进制为00000101 ,则表示第 1 个人和第 3 个人这个分组的和谐度,第 31 行则为 1~5 在一起的和谐度)

输出格式

一行一个整数,为最大和谐度和。

样例

输入样例

3 
41 
12 
57 
94 
89 
23 
12

输出样例

151 

数据范围与提示

对于 100% 数据,满足1<=n<=16 ,1<=每个组的和谐度<=1e6,输入均为整数。

解析

对于本题,很容易的想到dp[i]表示i状态的最大和谐度,最终答案为dp[(1<<n)-1],但如果只是暴力(枚举所有状态,判断是否是当前状态的子集)是不行的,复杂度为O(4n)会超时

但如果能够直接枚举当前状态的所有子集(子集枚举),根据二项式定理可得(我也不知道怎么得出来的),复杂度就降到了O(3n)

那么如何进行?

设 i 为当前状态,for(int j=i;j>0;j=(j-1)&i),枚举出来的j就是所有子集

正确性说明:

j 从 i 开始从大到小每次-1,而且&运算之后一定不会更大,所以 j 在循环里单调不上升

每次-1之后最低位的1会变成0,更低位会产生新的1,新的1会被&运算删掉,这样就枚举出一个子集

此后 j 一直变小,相当于高位的1慢慢向低位移动

例如:枚举101010的子集

1 101000
2 100010
3 100000
4 001010
5 001000
6 000010
7 000000

tips:i^j在 j 是 i 的子集是和i-j等效

30opt暴力代码

这里实际上枚举的是当前状态和可以继续叠加的状态,并不是枚举子集再判断,但是本质一样

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
int n,a[1<<16];
ll dp[1<<16];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<(1<<n);i++)
		scanf("%d",&a[i]);
 
	for(int i=0;i<(1<<n);i++)
		for(int j=0;j<(1<<n);j++)
		{
			if(i&j) continue;
			dp[i|j]=max(dp[i|j],dp[i]+dp[j]);
		}
	printf("%lld",dp[(1<<n)-1]);
	return 0;
}

 100pts代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
int n,a[1<<16];
ll dp[1<<16];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<(1<<n);i++)
		scanf("%d",&a[i]),dp[i]=a[i];
 	/*
	for(int i=0;i<(1<<n);i++)
		for(int j=0;j<(1<<n);j++)
		{
			if(i&j) continue;
			dp[i|j]=max(dp[i|j],dp[i]+a[j]);
		}
	*/
	for(int i=0;i<(1<<n);i++)//枚举目标状态 
		for(int j=i;j>0;j=(j-1)&i)//枚举目标状态子集 
		{
			dp[i]=max(dp[i],dp[j]+dp[i^j]);
		}
	printf("%lld",dp[(1<<n)-1]);
	return 0;
}
原文地址:https://www.cnblogs.com/conprour/p/15048901.html