C题:Electric Fence
题目描述
在本题中,格点是指横纵坐标皆为整数的点。
为了圈养他的牛,农夫约翰(Farmer John)建造了一个三角形的电网。他从原点(0,0)牵出一根通电的电线,连接格点(n,m)(0<=n<32000,0<m<32000),再连接格点(p,0)(p>0),最后回到原点。
牛可以在不碰到电网的情况下被放到电网内部的每一个格点上(十分瘦的牛)。如果一个格点碰到了电网,牛绝对不可以被放到该格点之上(或许Farmer John会有一些收获)。那么有多少头牛可以被放到农夫约翰的电网中去呢?
输入格式
输入文件只有一行,包含三个用空格隔开的整数:n,m和p。
输出格式
输出文件只有一行,包含一个整数,代表能被指定的电网包含的牛的数目。
样例输入
7 5 10
样例输出
20
题意:
求围成的三角形内部的点
思路一(数学计算):
通过计算得到两条直线方程,枚举纵坐标得到左右端点,结果相加即可
在计算过程中左端点始终+1,右端点要考虑实数情况和整数情况:整数时-1,实数时取整即可
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int x,y,p;
int countl(int pos){
return pos*x/y+1;
}
int countr(int pos){
double tmp=pos*1.0*(x-p)/y+p;
if(tmp==(int)tmp){
return (int)tmp-1;
}
else{
return (int)tmp;
}
}
int main()
{
IOS
cin>>x>>y>>p;
int ans=0;
for(int i=1;i<y;i++){
ans+=countr(i)-countl(i)+1;
}
cout<<ans<<endl;
return 0;
}
思路二(Pick定理):
1.Pick定理:s = a + b / 2 - 1,其中s是格点多边形的面积,a是多边形内部格点数,b是多边形边界上格点数。
2.扩展欧几里得:在两整点 A ( x1,y1 ) 和 B ( x2,y2 )的线段中经过整点的个数为 gcd(| x1-x2 |,| y1-y2 |)-1(不包括端点)。
对这里的扩展欧几里得的简单解释:
求出最大公约数g,也就是把y分为g个部分组成,x也是g个部分组成,这样的话,
从起点开始,每次横坐标和纵坐标分别增加相应的一部分,一直增到终点。
3.
可以把三角形的三条边分别看:
一,(0,0)→(p,0) 上有 p 个整点。
二,(n,m)→(0,0) 上有 gcd(n,m)个整点。
三,(p,0)→(n,m) 上有 gcd(|p-n|,m) 个整点。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int gcd(int x,int y) {
if(x%y==0){
return y;
}
else{
return gcd(y,x%y);
}
}
int main()
{
int n,m,p;
scanf("%d%d%d",&n,&m,&p);
int x=gcd(n,m)+gcd(abs(p-n),m)+p;
int s=(p*m)/2;
int y=s-x/2+1;
printf("%d\n",y);
return 0;
}
I题:线段相交Ⅲ
描述
线段相交有两种情形:一种是“规范相交”,另一种是“非规范相交”。规范相交是指两条线段恰有唯一一个不是端点的公共点。即如果一条线段的端点在另一条线段上则不视为相交。如果两条线段有部分重合,也不视为相交。而非规范相交则把以上两种情况都视为相交。如下图所示:
规范相交认为a,b两种情况都是不相交的,而非规范相交认为a,b两种情况都是相交的。
本题要求判断两条线段是否相交。如果是规范相交则输出YES,并输出交点坐标,如果是非规范相交则只需输出YES,如果不相交则输出NO。
输入
输入有多组数据,T表示输入数据的组数。每组测试数据有两行第一行输入一条线段的两个端点的坐标,第二行输入另一个线段的两个端点的坐标。
输出
对于每组测试数据,输出一行。如果是规范相交则输出YES,并输出交点坐标(小数点后面保留3位),如果是非规范相交则只需输出YES,如果不相交则输出NO。
样例输入
4
0 0 1 1
0 1 1 0
0 0 2 2
2 2 3 3
0 0 2 2
1.5 1.5 3 3
0 0 1 1
2 2 3 3
样例输出
YES (0.500,0.500)
YES
YES
NO
题意:判断两条线段的关系,若是相交输出YES,否则输出NO,若是严格相交,则再输出坐标
思路:
根据题目要求,我们可以分步得到最终效果
1.判断是否相交
(一)快速排斥实验:就是看看这两条线段是不是完全没关系(判断两条线段在 x 以及 y 坐标的投影是否有重合)
(二)跨立实验:如果c,d两点在线段ab两端。并且a,b两点,在线段cd两端,那么就能说明两条直线相交。并且我们知道,向量叉乘可以判断一个线段在另一个线段的顺时针方向还是逆时针方向,如果cb和bd在ab的相反方向。也就是说向量叉乘的结果一个是正,一个是负。叉乘的值相乘如果是个负值,那就说明一定是cd在ab的两端了,同理,为了避免个别情况,也要判断一下ab在cd两端。
2.判断是否严格相交
这里考虑了两种情况:端点和端点相交,端点和线段相交
前者直接带进去比较,后者根据两点算出直线方程,带进去即可
3.若是严格相交,求出点的坐标
求法由数学公式推导
设线段一为:a1x+b1y+c1=0;带入(x1,y1),(x2,y2),可得a=y1-y2;b=x2-x1;c=x1*y2-y1*x2;
同理可得线段二为:a2x+b2y+c2=0;a=y3-y4;b=x4-x3;c=x3*y4-y3*x4;
联立方程组解得:
x=(b1*c2-b2*c1)/(a1*b2-b1*a2);
y=(a2*c1-a1*c2)/(a1*b2-b1*a2);
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
const double eps=1e-6;
const int N=105;
typedef struct node{
double x,y;
}node;
bool jd(node a,node b,node c,node d){
if(min(a.x,b.x)>max(c.x,d.x)||min(a.y,b.y)>max(c.y,d.y)||min(c.x,d.x)>max(a.x,b.x)||min(c.y,d.y)>max(a.y, b.y))
return 0;
double h,i,j,k;
h=(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
i=(b.x-a.x)*(d.y-a.y)-(b.y-a.y)*(d.x-a.x);
j=(d.x-c.x)*(a.y-c.y)-(d.y-c.y)*(a.x-c.x);
k=(d.x-c.x)*(b.y-c.y)-(d.y-c.y)*(b.x-c.x);
return h*i<eps&&j*k<eps;
}
bool same(node a,node b){
if(a.x==b.x&&a.y==b.y)return true;
return false;
}
bool dot(node a,node b,node c){
return (c.y-a.y)*(a.x-b.x)==(a.y-b.y)*(c.x-a.x);
}
int main()
{
node a,b,c,d;
double a0,a1,b0,b1,c0,c1,res,xx,yy;
int t;
cin>>t;
while(t--){
scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&a.x,&a.y,&b.x,&b.y,&c.x,&c.y,&d.x,&d.y);
if(jd(a,b,c,d)==0)printf("NO\n");
else{
if(same(a,c)||same(a,d)||same(b,c)||same(b,d)){
printf("YES\n");
}
else if(dot(a,b,c)||dot(a,b,d)||dot(c,d,a)||dot(c,d,b)){
printf("YES\n");
}
else{
a0=a.y-b.y;
b0=b.x-a.x;
c0=a.x*b.y-b.x*a.y;
a1=c.y-d.y;
b1=d.x-c.x;
c1=c.x*d.y-d.x*c.y;
res=a0*b1-a1*b0;
xx=(b0*c1-b1*c0)/res;
yy=(a1*c0-a0*c1)/res;
printf("YES (%.3lf,%.3lf)\n",xx,yy);
}
}
}
return 0;
}