您的位置:首页 > 其它

poj 1873 && uva 811 && la 5211

2016-07-28 00:17 309 查看

题目概述

在直角坐标系中,有N棵可视为点的树,每棵树都有自己的编号,需要砍掉其中一部分来造围栏围住剩下的树,每棵树的坐标x,y,价值v,砍掉后可建造围栏长度l,问砍掉哪些树可以在损失价值最小的情况下围住剩下的树,以及会余下可造多长围栏的木头

损失相同的情况下以砍树最少的为准

时限

1000ms/3000ms

输入

第一行正整数N,其后N行,每行4个正整数x,y,v,l,每棵树的编号为其在该组数据中出现的序号,从1开始,输入到N=0结束

限制

2<=N<=15;0<=v,l<=10000

输出

每组数据输出在三行中,第一行为字符串

Forest #

其中#为数据序数,从1开始,第二行为字符串

Cut these trees:

其后跟若干个数,为树的编号,指代这些树需要被砍倒,每个数前有一空格,第三行为字符串

Extra wood: #

其中#为一保留两位小数的浮点数,为砍倒的树造完围栏后剩下的木材可建造围栏长度,其之前带一空格,两组输出间有一个空行

样例输入

6

0 0 8 3

1 4 3 2

2 1 7 1

4 1 2 3

3 5 4 6

2 3 9 8

3

3 0 10 2

5 5 20 25

7 -3 30 32

2

0 0 8 3

1 4 3 2

0

样例输出

Forest 1

Cut these trees: 2 4 5

Extra wood: 3.16

Forest 2

Cut these trees: 2

Extra wood: 15.00

Forest 3

Cut these trees: 2

Extra wood: 2.00

讨论

计算几何,求凸包,外加一点二进制优化的枚举

先说二进制优化,数据规模不大,15棵树,一共2^15=32768种情况,排除全砍和没动两种极端以及后面的剪枝,很多计算都可以避开,用1和0表示保留和砍倒,放在一个unsigned sh
ed7a
ort即可,和poj 1753有点类似

接下来是剪枝以及一些预处理,首先是排序,排序在输入结束后立刻进行,按水平序排序,需要且只能排一次,后面会写如何使用,对于每种情况,算出砍倒的树的总价,如果超过之前总价最低的一次,直接continue,因为即便算了也无法刷新最低总价,通过这个剪枝,越早碰到最优情况,剪枝幅度越大,算15个数的和可比求凸包要快不少,至于题目要求说对于花费相同的情况,由于要求数量最少而不是编号最小,因而还需要额外几行代码才能比,不过经过测试,这样的情况并不存在,而题解代码中也忽略了这个处理

算法的核心,求凸包,由于题目原来的编号没法直接用,只能用数组下标,但graham’s scan如果用极角排序由于是相对于某个存在的点,每种情况可能都需要排序,除了复杂度骤升,也会使数组下标和元素的关系破裂,必须换,改用水平序的andrew算法,只需要排一次,而代价是凸包需要求两趟,一趟正序构造一半,一趟逆序构造另一半,但这反过来使得树被砍得只剩下1或2棵的情况不再需要特殊处理,对于2棵,由于求两趟,刚好围了两次,这点可以利用第二组样例测试,对于1棵,仅仅压栈了一个点,但由于总距离以0初始化,for循环直接跳过后正好是所求,这点可以利用第三组样例测试

凸包及其周长求得后,判断砍掉的树是否够造围栏,如果够,一并记录下二进制值(非常重要),总花费,余料合计,便于输出和剪枝

对于输出部分,由于要按编号升序输出,为了方便,额外开一个布尔数组进行转换,然后升序扫一下就可以了

题目其实并不难,但是对于额这种新手,真的是罕见的好题,平时罕见需要二进制的题,也少有如此大幅剪枝的情况,虽说也是各处找题解才学到二进制,水平序排序,也因为一个自己挖小坑和两个交错题号的题贡献两个WA一个CE,但也算是低于平均提交了

之前写的代码太乱套,后来重新做了一遍,代码简化了一些

题解状态

192K,79MS,C++,1999B

题解代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 16
#define memset0(a) memset(a,0,sizeof(a))

struct Pt//树的结构
{
int n, x, y, v, l;//树的编号 横纵坐标 价值 等价围栏长度
bool operator<(const Pt &b)const//水平序排序 方便求凸包
{
return y != b.y ? y < b.y : x < b.x;
}
}pts[MAXN];
int N;//树总数
int stk[MAXN], top;//凸包算法用的栈
bool mk[MAXN];//调整输出顺序的数组
int xp(Pt &a, Pt &b, Pt &c)//向量积 改用点来表示 简洁一点
{
return (a.x - b.x)*(c.y - b.y) - (a.y - b.y)*(c.x - b.x);
}
double dis(Pt &a, Pt &b)//两点间距离
{
return sqrt(double(a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
void fun()
{
for (int p = 0; p < N; p++) {
scanf("%d%d%d%d", &pts[p].x, &pts[p].y, &pts[p].v, &pts[p].l);//input
pts[p].n = p;//赋予树一个编号
}
sort(pts, pts + N);//水平序排序
pts
= pts[0];//为了避免求模而将首尾设置为同一棵树
int leastv = INF;//least_value 砍树最便宜时砍掉的树的总价
double leastr;//least_rest 最优时剩余木料
unsigned short leastB;//least_Bytes 最优时树的存留状态 0是砍掉 1是保留 15棵树用short刚好够
for (unsigned short B = 1; B < (1 << N) - 1; B++) {//枚举每种砍树状况 除了全砍和全留
int sumv = 0, suma = 0;//sum_value sum_available 这种情况下砍的树的总价 砍的树可造围栏长度
for (int p = 0; p < N; p++)
if (~B&(1 << p)) {//利用位反和位与取出被砍掉的树
sumv += pts[p].v;
suma += pts[p].l;
}
if (sumv > leastv)//当砍掉的总价不是最优时便不用再算 这是重要剪枝
continue;
top = 0;//下面是andrew凸包算法主体
for (int p = 0; p < N; p++)
if (B&(1 << p)) {//只有保留的树才算
while (top > 1 && xp(pts[p], pts[stk[top - 2]], pts[stk[top - 1]]) >= 0)
top--;
stk[top++] = p;
}
int stklen = top;
for (int p = N - 1; p >= 0; p--)
if (B&(1 << p)) {
while (top > stklen&&xp(pts[p], pts[stk[top - 2]], pts[stk[top - 1]]) >= 0)
top--;
stk[top++] = p;
}
top--;//第一棵树多算了一次 去掉
double suml = 0;//sum_length 凸包长度 也就是剩下的树需要多长围栏
for (int p = 0; p < top; p++)
suml += dis(pts[stk[p]], pts[stk[p + 1]]);
if (suml < suma) {//当围栏足够时更新最优解 不是最优的都被剪枝掉了
leastv = sumv;
leastr = suma - suml;
leastB = B;
}
}
printf("Cut these trees:");
for (int p = 0; p < N; p++)//利用mk数组调整输出顺序
if (~leastB&(1 << p))
mk[pts[p].n] = 1;
for (int p = 0; p < N; p++)
if (mk[p])
printf(" %d", p + 1);//output
printf("\nExtra wood: %.2lf\n", leastr);//output
}
int main(void)
{
//freopen("vs_cin.txt", "r", stdin);
//freopen("vs_cout.txt", "w", stdout);

bool f = 0;
int times = 0;
while (~scanf("%d", &N) && N) {//input
if (f)
printf("\n");//output
printf("Forest %d\n", ++times);//output
fun();
memset0(mk);
f = 1;
}
}


EOF
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: