zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    HTML: vertical algin Big/small div in same row (bootstrap)
    unix时间转换
    chrome工具分析
    DNF 包管理器
    安装nodejs
    location属性解释
    angular深入理解途径
    ui-router与ngRoute
    angular $location服务获取url
    Python文件操作
  • 原文地址:https://www.cnblogs.com/conprour/p/15048901.html
Copyright © 2011-2022 走看看