【dp理解还不够】状态dp 跟运气策略

3 篇文章 0 订阅

http://acm.hdu.edu.cn/showproblem.php?pid=5135

题意:给n条木棒, 然后让你组成三角形  木棒不能重复 求最大面积和。

这题训练赛我们是猜了一个贪心策略 就ac了

把n条木棒从大到小排序  然后依次取三条 看能否组成三角形  能就加上其面积

不能就舍弃最大边   因为我们想 三角形的三条边差值越少  面积就越大。  就好比等边三角形  三条边相等 相差越少

传统做法是状态dp  利用二进制状态压缩  往下看。。。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <functional> 
#include <cmath>
using namespace std;

const int maxn = 20;
int stick[maxn];

bool judge(int a, int b, int c){
	if (a + b > c && a + c > b && b + c > a)
		return true;
	return false;
}

double area(int a, int b, int c){
	double p = (a + b + c) * 1.0 / 2.0;
	return sqrt(p * (p - a) * (p - b) * (p - c));
}
int main() {
	int n;
	while (~scanf("%d", &n)) {
		if (!n) break;
		for (int i = 0; i < n; ++i) {
			scanf("%d", &stick[i]);
		}
		sort(stick, stick + n, greater<int> ());
//		for (int i = 0; i < n; ++i) {
//			printf("%d ", stick[i]);
//		}
		double ans = 0;
		for (int i = 0; i <= n - 3; ++i) {
			if (judge(stick[i], stick[i + 1], stick[i + 2])) {
				ans += area(stick[i], stick[i + 1], stick[i + 2]);
				i += 2;
			}
		}	
		printf("%.2f\n", ans);	
	}
	return 0;
}

赛后看题解  是用状态dp

状态dp  是 离不开二进制的

二进制是个好东西  比如十进制3  在二进制中就有000  001  010  011 100 101 110 111   8种状态 即子集个数 2的n次方

然后我们基于二进制  从右到左  数  从0开始   第0位  第1位。。。第i位    对应着第i+1个木棒(没有第0个木棒嘛)

注意111   =  (1<<n)-1   

所以我们来一个三重循环(循环有技巧,这样循环完全不会重复,图论也经常这样把n个点的所有无向边枚举完)   把所有能组成三角形的方案 给一种状态(有技巧,木棒都被下标0 1 2 3 。。。标记。那选完三条边后,比如选了0 2 5木棒 那tag的二进制就是100101  状态也不会重复 唯一的)

 

怎么dp法!!!

首先1101111可以由1101000和0000111这两个三角形转换而来   或1000011和0101100  等等  (前提当然是这样的棒能组成三角形)

那可能存在1110   0111这样的棒组成三角形  这些是什么  这两个三角形都用到了1号棒2号棒    而题目说不能重复用棒

解决方案:设i为一种三角形方案  j为另外一种方案  我们知道1&0=0   1&1=1   0&0=0  

我们想要i  j的二进制表示对应的位都是  0对1  0对0      永远没有1对1

所以想到       如果i & j  跳过

怎么dp求    首先开dp【1<< 木棒个数】  

现在知道三角形的方案 以及他们的状态号  保存在一个vector

两个for(枚举所有三角形)  不  这样只是取这些木棒中  两个三角形的最大面积和  

难道k个循环?这样就k个三角形的最大面积和

但k是未知的 就算拿k=三角形的方案  也能求  就是   k的k次方复杂度   恐怖。。。

 

收获:

dp【三角形状态号】=已经保存了该三角形的面积

 

for(所有方案 这不是三角形的方案  而是【0,1<<n)   )

for(所有三角形方案){

if(边重复用)跳过

看看转移的状态能否更新

 }

最后输出dp【(1<<n)-1】

 

帮助理解:首先从0方案开始遍历 是必须的  这样第二个for  可以转移到后面的方案 从而更新 维护

当第一个for去到这些方案时  它要做的就是 能不能再转移到更后面的方案  这完全没有后效型

 

我当时想 为什么输出dp【(1<<n)-1】  如n=4  为什么输出dp【1111】

4个木棒 如果有三角形方案0111  那他的结果不应该是dp【0111】吗

再次说明从0方案开始遍历 是必须的  第一个for一定会去到1000  而dp【1000】初始化0

所以dp【1000 | 0111】=dp【1111】 = dp【0111】 =结果

 

一句话:小方案会转移到大方案  当遍历到大方案时 会转移到更大的方案

当前遍历的方案 一定是最优的

 

#include <iostream> 
#include <cstring>
#include <vector>
#include <cmath>
#include <algorithm> 
#include <iomanip>

using namespace std;

int stick[20];
double dp[1<<12];
//一共就12条木棒 二进制12个1就是   (1<<12) - 1   二进制第i位为1代表选择第i个木棒  0代表没选  i从0开始 即i代表第i+1个物体 
//题目要求在这些木棒中 选  求组成三角形的面积和最大值  那意味木棒不能重复用 

bool triangle (int a, int b, int c) {
	return ((a + b > c)&&(a + c > b)&&(b + c > a));
}

double area(int a, int b, int c) {
	double p = (a + b + c) * 0.5;
	return sqrt(p * (p - a) * (p - b) * (p - c));
}
int main(){
	int n;
	while (cin >> n && n){
		vector<int> v;
		v.clear();
		memset(dp, 0, sizeof(dp));
		for (int i = 0; i < n; ++i) cin >> stick[i];
		sort(stick, stick + n);
		for (int i = 0; i <= n - 3; ++i) {
			for (int j = i + 1; j <= n - 2; ++j) {
				for (int k = j + 1; k <= n - 1; ++k) {
					if (triangle(stick[i], stick[j], stick[k])) {
						int tag = (1 << i) | (1 << j) | (1 << k);
						dp[tag] = area(stick[i], stick[j], stick[k]);
						v.push_back(tag);
					}
				}
			}
		}
		
		
		
		for (int i = 0; i < (1 << n); ++i) {
			for (int j = 0; j < v.size(); ++j) {
				//超关键  现在知道所有三角形的状态 但存在边的重复  1&0=0 1&1=1 0&0=0  
				//为了让边不重复   即状态转移只能发生在对应二进制位 都是01 00      11绝对不行   
				if (i & v[j])
					continue;
				dp[i | v[j]] = max(dp[i | v[j]], dp[i] + dp[v[j]]); 
				
			}
		}
		cout << fixed << setprecision(2) << dp[(1 << n) - 1] << endl;
	}
	
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值