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: #
其中#为一保留两位小数的浮点数,为砍倒的树造完围栏后剩下的木材可建造围栏长度,其之前带一空格,两组输出间有一个空行
样例输入
60 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 1Cut 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
相关文章推荐
- 启动eclipse报错 Could not create the Java Virtual Machine
- JVM的栈上分配与逃逸分析(Escape Analysis)
- 浅析EL(Expression Language)
- 一个动画怎么实现
- 转场动画
- 哥尼斯堡七桥问题
- linux 1-1 1-2 VMware Workstation 使用、操作系统
- Unity 协同函数、WWW
- Java内存区域与异常
- 2016暑假集训
- Android_UI:ViewPager
- 写一个函数返回参数二进制中 1 的个数
- codeforce 448D Multiplication Table
- Redis笔记二之Redis命令操作简介及五种value数据类型
- 《黑客与画家》读书笔记
- Rank-Tree带动态查询删除 Splay
- 异或思维题--to xor or not to xor nkoj3734
- 打印环境变量和参数列表的方法
- uva 644
- java之位运算基础