fastle
垆边人似月 皓腕凝霜雪

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)",&notex[i],&notey[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",&note[i].x,&note[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",&note[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;

}

 

posted on 2017-08-09 20:59  fastle  阅读(422)  评论(0编辑  收藏  举报