2017年8月9日 计算几何 知识点
张炳琪
一.知识点相关
几道有关几何的题目,杂题
二.今天完成8道题目
1.洛谷P2181 对角线 入门难度 几何推论
2.洛谷P1355 神秘大三角 普及/提高- 几何推论
3.洛谷P1429 平面最近点对(加强版) 提高+省选- 几何分治
4.洛谷P2735 电网 Electric Fences 普及/提高- 几何推论
5.洛谷 P2726 阶乘 Factorials 普及提高- 模拟
6.洛谷 P2725 邮票 Stamps 普及/提高- 动态规划
7.洛谷P2732 商店购物 Shopping Offers 提高+省选- 动态规划
8.洛谷 P2731 Riding the Fences 普及+提高 欧拉路
三、不会的题目是
四、以下贴作业的题目、解析及代码(要有序列号)
1. P2181 对角线
题目描述
对于一个N个定点的凸多边形,他的任何三条对角线都不会交于一点。请求楚图形中对角线交点的个数。
例如,6边形:
输入输出格式
输入格式:
第一行一个n,代表边数。
输出格式:
第一行输出交点数量
输入输出样例
输入样例#1:
3
输出样例#1:
0
输入样例#2:
6
输出样例#2:
15
说明
50%的测试数据 3≤N≤100;
100%的测试数据 3≤N≤100000.
方法::/*
这属于计算几何中的推论公式吧,任何凸n边形中对角线焦点个数
等于(n)*(n - 1)*(n - 2)*(n - 3) / 24; 也就是C n-4 n
*/
代码::
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int main()
{
unsigned long long n;
cin >> n;
cout<< n * (n - 1) / 2 * (n - 2) / 3 *(n - 3)/ 4 <<endl;
return 0;
}
P1355 神秘大三角
题目描述
判断一个点与已知三角形的位置关系。
输入输出格式
输入格式:
前三行:每行一个坐标,表示该三角形的三个顶点
第四行:一个点的坐标,试判断该点与前三个点围成三角形的位置关系
(详见样例)
所有坐标值均为整数。
输出格式:
若点在三角形内(不含边界),输出1;
若点在三角形外(不含边界),输出2;
若点在三角形边界上(不含顶点),输出3;
若点在三角形顶点上,输出4。
输入输出样例
输入样例#1:
(0,0)
(3,0)
(0,3)
(1,1)
输出样例#1:
1
说明
【数据规模与约定】
对于100%数据,0<=所有点的横、纵坐标<=100
方法::
/*
判断一个点和给定三角形的 位置关系 可以先判断是否在三个顶点上g
如果不是的话 将这个点分别和另外三个点连边,然后计算连边后
得到的三个小三角形的面积和,若等于原三角形面积和 则在三角形内部
如果某个三角形面积是0 则在三角形边上否则就在三角形外部
*/
代码::#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
double notex[123],notey[123];
double qiu(int a,int b)
{
return sqrt((notex[a] - notex[b]) * (notex[a] - notex[b]) + (notey[a] - notey[b]) * (notey[a] - notey[b]));
}
double work(int a,int b,int c)
{
double aa = qiu(a,b);
double bb = qiu(b,c);
double cc = qiu(a,c);
double p = (aa + bb + cc) / 2.0;
double ans = sqrt(p * (p - aa) * (p - bb) * (p - cc));
return (int) (ans * 100 + 0.5);
}
int main()
{
for(int i = 1;i <= 4;i++)
{
char c;
cin >> c >> notex[i] >> c >> notey[i] >> c;
//scanf("(%lf,%lf)",¬ex[i],¬ey[i]);
}
if((notex[4] == notex[1] && notey[4] == notey[1]) ||
(notex[4] == notex[2] && notey[4] == notey[2]) ||
(notex[4] == notex[3] && notey[4] == notey[3]) )
{
printf("4");
return 0;
}
int yuan = work(1,2,3);
int s1 = work(1,3,4);
int s2 = work(1,2,4);
int s3 = work(2,3,4);
if(s1 + s2 + s3 > yuan)
{
printf("2");
return 0;
}
if(s1 == 0 || s2 == 0 || s3 == 0)
printf("3");
else
printf("1");
return 0;
}
P1429 平面最近点对(加强版)
题目描述
给定平面上n个点,找出其中的一对点的距离,使得在这n个点的所有点对中,该距离为所有点对中最小的
输入输出格式
输入格式:
第一行:n;2≤n≤200000
接下来n行:每行两个实数:x y,表示一个点的行坐标和列坐标,中间用一个空格隔开。
输出格式:
仅一行,一个实数,表示最短距离,精确到小数点后面4位。
输入输出样例
输入样例#1:
3
1 1
1 2
2 2
输出样例#1:
1.0000
说明
0<=x,y<=10^9
方法::
//平面 最近 点对 的做法 是将所有的点 按照 一个坐标 二分 ,之后再二分等等 ,\
对于每一块中 我们可以求出最近点对的距离(二分成一个点时,距离返回无限大) \
比较难的操作 是将 原本的分开的点合并的方法 朴素的方法没什么优势 我们能够首先扫一遍\
将与对称轴距离已经超过答案的 点 去掉 ,然后将剩余的点 按照 y的顺序排序,两个循环合并\
在不满足某些情况的时候 可以提前跳出 节省时间 而经过大佬的推算 每次合并的点的个数不会超过六对
代码::
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#define MAXN 200001
#define INF 0x7ffffff
using namespace std;
struct Note
{
double x;
double y;
}note[MAXN];
int n;
int stack[MAXN];
inline double MIN(double a, double b)
{
return a < b?a : b;
}
inline double ABS(double a)
{
return a > 0?a:-a;
}
bool cmp(Note a, Note b)
{
return a.x == b.x?a.y < b.y:a.x < b.x;
}
bool recmp(int a, int b)
{
return note[a].y < note[b].y;
}
inline double ask(int a,int b)
{
return sqrt((note[a].x - note[b].x) * (note[a].x - note[b].x) + (note[a].y - note[b].y) * (note[a].y - note[b].y));
}
double unionn(int l, int r)
{
if(l == r)
{
return INF;
}
int top = 0;
int mid = (l + r) >> 1;
double ans = MIN(unionn(l,mid),unionn(mid + 1,r));
for(int i = l; i <= r; i++)
{
if(ABS(note[i].x - note[mid].x) <= ans)
{
stack[++top] = i;
}
}
sort(stack + 1, stack + top + 1,recmp);
for(int i = 1; i <= top; i++)
{
for(int j = i + 1; j <= top && note[stack[j]].y - note[stack[i]].y < ans;j ++)
{
double op = ask(stack[i],stack[j]);
if(ans > op)ans = op;
}
}
return ans;
}
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i ++)
{
scanf("%lf%lf",¬e[i].x,¬e[i].y);
}
sort(note + 1,note + n + 1,cmp);
printf("%.4lf",unionn(1,n));
return 0;
}
P2735 电网 Electric Fences
题目描述
在本题中,格点是指横纵坐标皆为整数的点。
为了圈养他的牛,农夫约翰(Farmer John)建造了一个三角形的电网。他从原点(0,0)牵出一根通电的电线,连接格点(n,m)(0<=n<32000,0<m<32000),再连接格点(p,0)(p>0),最后回到原点。
牛可以在不碰到电网的情况下被放到电网内部的每一个格点上(十分瘦的牛)。如果一个格点碰到了电网,牛绝对不可以被放到该格点之上(或许Farmer John会有一些收获)。那么有多少头牛可以被放到农夫约翰的电网中去呢?
输入输出格式
输入格式:
输入文件只有一行,包含三个用空格隔开的整数:n,m和p。
输出格式:
输出文件只有一行,包含一个整数,代表能被指定的电网包含的牛的数目。
输入输出样例
输入样例#1:
7 5 10
输出样例#1:
20
说明
题目翻译来自NOCOW。
USACO Training Section 3.4
方法::
/*
首先这个题目要依靠的一个定理叫做皮克定理 原版的皮克定理用于 求格点中的多边形
的面积 公式可以表示为2S=2a+b-2,其中a表示多边形内部的点数,b表示多边
形边界上的点数,s表示多边形的面积。
然后这里用到的推论是 三角形斜边经过的个点 等于两直角边 长度的最大公约数
我们用普通方法求出面积 在用以上方法求出边上的点 就能求出内部的点了
*/
代码::
#include<cstdio>
#include<algorithm>
using namespace std;
int gcd(int a,int b)
{
return b == 0 ? a : gcd(b,a % b);
}
int main()
{
int n,m,p;
scanf("%d%d%d",&n,&m,&p);
int s = p * m / 2;
int bian = (gcd(n,m) + gcd(abs(p - n),m) + p);
printf("%d",s - bian / 2 + 1);
return 0;
}
P2726 阶乘 Factorials
题目背景
N的阶乘写作N!,表示小于等于N的所有正整数的乘积。
题目描述
阶乘会变大得很快,如13!就必须用32位整数类型来存储,到了70!即使用浮点数也存不下了。 你的任务是找到阶乘最前面的非零位。举个例子:
5!=1*2*3*4*5=120,所以5!的最靠后的非零位是2。
7!=1*2*3*4*5*6*7=5040,所以最靠后的非零位是4。
输入输出格式
输入格式:
共一行,一个不大于4,220的正整数N
输出格式:
共一行,输出N!最靠后的非零位。
输入输出样例
输入样例#1:
7
输出样例#1:
4
说明
题目翻译来自NOCOW。
USACO Training Section 3.2
方法::
/*
模拟进行操作 每次求完不断除以十直到后面没有0 然后再模10
*/
代码::
#include<cstdio>
#include<algorithm>
using namespace std;
int main()
{
int zz = 1;
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
zz *= i;
while(zz % 10 == 0)zz /= 10;
zz %= 1000;
}
printf("%d",zz % 10);
return 0;
}
P2725 邮票 Stamps
题目背景
给一组 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K —— 表示信封上能够贴 K 张邮票。计算从 1 到 M 的最大连续可贴出的邮资。
题目描述
例如,假设有 1 分和 3 分的邮票;你最多可以贴 5 张邮票。很容易贴出 1 到 5 分的邮资(用 1 分邮票贴就行了),接下来的邮资也不难:
6 = 3 + 3
7 = 3 + 3 + 1
8 = 3 + 3 + 1 + 1
9 = 3 + 3 + 3
10 = 3 + 3 + 3 + 1
11 = 3 + 3 + 3 + 1 + 1
12 = 3 + 3 + 3 + 3
13 = 3 + 3 + 3 + 3 + 1
然而,使用 5 枚 1 分或者 3 分的邮票根本不可能贴出 14 分的邮资。因此,对于这两种邮票的集合和上限 K=5,答案是 M=13。 [规模最大的一个点的时限是3s]
小提示:因为14贴不出来,所以最高上限是13而不是15
输入输出格式
输入格式:
第 1 行: 两个整数,K 和 N。K(1 <= K <= 200)是可用的邮票总数。N(1 <= N <= 50)是邮票面值的数量。
第 2 行 .. 文件末: N 个整数,每行 15 个,列出所有的 N 个邮票的面值,每张邮票的面值不超过 10000。
输出格式:
第 1 行:一个整数,从 1 分开始连续的可用集合中不多于 K 张邮票贴出的邮资数。
输入输出样例
输入样例#1:
5 2
1 3
输出样例#1:
13
说明
题目翻译来自NOCOW。
USACO Training Section 3.1
/*
这个题目可以用背包来做 做法是记录当前状态的最少需求
转移的时候求min即可 注意的是当只有 在要求的件数之内才能用
*/
代码::
#include<cstdio>
#include<algorithm>
using namespace std;
int dp[7234567];
int main()
{
int k,n;
scanf("%d%d",&k,&n);
for(int i = 1;i <= 2230000;i++)
{
dp[i] = 0x7ffffff;
}
for(int i = 1;i <= n;i++)
{
int op;
scanf("%d",&op);
for(int j = op;j <= 2230000;j++)
{
if(dp[j - op] < k)
{
dp[j] = min(dp[j],dp[j - op] + 1);
}
}
}
for(int i = 1;i <= 2230000;i++)
if(dp[i] == 0x7ffffff)
{
printf("%d",i - 1);
return 0;
}
}
P2732 商店购物 Shopping Offers
题目背景
在商店中,每一种商品都有一个价格(用整数表示)。例如,一朵花的价格是 2 zorkmids (z),而一个花瓶的价格是 5z 。为了吸引更多的顾客,商店举行了促销活动。
题目描述
促销活动把一个或多个商品组合起来降价销售,例如:
三朵花的价格是 5z 而不是 6z, 两个花瓶和一朵花的价格是 10z 而不是 12z。 编写一个程序,计算顾客购买一定商品的花费,尽量利用优惠使花费最少。尽管有时候添加其他商品可以获得更少的花费,但是你不能这么做。
对于上面的商品信息,购买三朵花和两个花瓶的最少花费的方案是:以优惠价购买两个花瓶和一朵花(10z),以原价购买两朵花(4z)。
输入输出格式
输入格式:
输入文件包括一些商店提供的优惠信息,接着是购物清单。(最多有5种商品)
第一行 优惠方案的种类数(0 <= s <= 99)。
第二行..第s+1 行 每一行都用几个整数来表示一种优惠方式。第一个整数 n (1 <= n <= 5),表示这种优惠方式由 n 种商品组成。后面 n 对整数 c 和 k 表示 k (1 <= k <= 5)个编号为 c (1 <= c <= 999)的商品共同构成这种优惠,最后的整数 p 表示这种优惠的优惠价(1 <= p <= 9999)。优惠价总是比原价低。
第 s+2 行 这一行有一个整数 b (0 <= b <= 5),表示需要购买 b 种不同的商品。
第 s+3 行..第 s+b+2 行 这 b 行中的每一行包括三个整数:c,k,p。 c 表示唯一的商品编号(1 <= c <= 999),k 表示需要购买的 c 商品的数量(1 <= k <= 5)。p 表示 c 商品的原价(1 <= p <= 999)。最多购买 5*5=25 个商品。
输出格式:
只有一行,输出一个整数:购买这些物品的最低价格。
输入输出样例
输入样例#1:
2
1 7 3 5
2 7 1 8 2 10
2
7 3 2
8 2 5
输出样例#1:
14
方法::
/*
完全背包问题 将每一个组合单独看成一种物品 最后的单个的也看做一种物品
然后 进行完全背包的dp就可以了
*/
代码::
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
struct Note{
int num[6];
int cost;
}note[1234];
int dp[6][6][6][6][6];
int ID[1001];
int idnum = 0;
int need[11];
int main()
{
int s;
scanf("%d",&s);
for(int i = 1;i <= s;i++)
{
int n;
scanf("%d",&n);
for(int j = 1;j <= n;j++)
{
int idid,ver;
scanf("%d%d",&idid,&ver);
if(ID[idid] == 0)
{
ID[idid] = ++idnum;
}
note[i].num[ID[idid]] = ver;
}
scanf("%d",¬e[i].cost);
}
int op;
scanf("%d",&op);
memset(dp,0x3e,sizeof(dp));
dp[0][0][0][0][0] = 0;
for(int i = 1;i <= op;i++)
{
s++;
int ci,bi,veri;
scanf("%d%d%d",&ci,&bi,&veri);
if(ID[ci] == 0)
{
ID[ci] = ++idnum;
}
note[s].num[ID[ci]] = 1;
note[s].cost = veri;
need[ID[ci]] = bi;
}
for(int o = 1;o <= s;o++)
for(int v1 = note[o].num[1];v1 <= need[1];v1++)
for(int v2 = note[o].num[2];v2 <= need[2];v2++)
for(int v3 = note[o].num[3];v3 <= need[3];v3++)
for(int v4 = note[o].num[4];v4 <= need[4];v4++)
for(int v5 = note[o].num[5];v5 <= need[5];v5++)
{
dp[v1][v2][v3][v4][v5] = min(dp[v1][v2][v3][v4][v5],
dp[v1 - note[o].num[1]]
[v2 - note[o].num[2]]
[v3 - note[o].num[3]]
[v4 - note[o].num[4]][v5 - note[o].num[5]] + note[o].cost);
}
printf("%d",dp[need[1]][need[2]][need[3]][need[4]][need[5]]);
return 0;
}
P2731 骑马修栅栏 Riding the Fences
题目背景
Farmer John每年有很多栅栏要修理。他总是骑着马穿过每一个栅栏并修复它破损的地方。
题目描述
John是一个与其他农民一样懒的人。他讨厌骑马,因此从来不两次经过一个栅栏。你必须编一个程序,读入栅栏网络的描述,并计算出一条修栅栏的路径,使每个栅栏都恰好被经过一次。John能从任何一个顶点(即两个栅栏的交点)开始骑马,在任意一个顶点结束。
每一个栅栏连接两个顶点,顶点用1到500标号(虽然有的农场并没有500个顶点)。一个顶点上可连接任意多(>=1)个栅栏。两顶点间可能有多个栅栏。所有栅栏都是连通的(也就是你可以从任意一个栅栏到达另外的所有栅栏)。
你的程序必须输出骑马的路径(用路上依次经过的顶点号码表示)。我们如果把输出的路径看成是一个500进制的数,那么当存在多组解的情况下,输出500进制表示法中最小的一个 (也就是输出第一位较小的,如果还有多组解,输出第二位较小的,等等)。
输入数据保证至少有一个解。
输入输出格式
输入格式:
第1行: 一个整数F(1 <= F <= 1024),表示栅栏的数目
第2到F+1行: 每行两个整数i, j(1 <= i,j <= 500)表示这条栅栏连接i与j号顶点。
输出格式:
输出应当有F+1行,每行一个整数,依次表示路径经过的顶点号。注意数据可能有多组解,但是只有上面题目要求的那一组解是认为正确的。
输入输出样例
输入样例#1:
9
1 2
2 3
3 4
4 2
4 5
2 5
5 6
5 7
4 6
输出样例#1:
1
2
3
4
2
5
4
6
5
7
说明
题目翻译来自NOCOW。
USACO Training Section 3.3
方法::
/*
这个题目应该也是比较简单 输入的边恰好能够组成欧拉回路
为了满足题意中的 最小的路径 我们用邻接矩阵来存边 并记录每个点的连边情况
按照一般求欧拉回路的套路来做即可
*/
代码::
#include<cstdio>
#include<algorithm>
#define MAXN 501
using namespace std;
int m[MAXN][MAXN];
int note[MAXN];
int path[12345];
int top = 0;
void dfs(int now)
{
for(int i = 1;i <= 500;i++)
{
if(m[now][i])
{
m[now][i] --;
m[i][now] --;
dfs(i);
}
}
path[++top] = now;
//path[++top] = now + 1;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
int vi,vj;
scanf("%d%d",&vi,&vj);
m[vi][vj]++;
m[vj][vi]++;
note[vi]++;
note[vj]++;
}
for(int i = 1;i <= 500;i++)
{
if(note[i] & 1)
{
dfs(i);
break;
}
}
for(int i = 1,j = 1;i <= 500;i++)
{
if(i + j != j + i)
printf("zbq is handsome\n");
}
if(top == 0)
dfs(1);
for(int i = top;i >= 1;i--)
printf("%d\n",path[i]);
return 0;
}