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;
}