“被破坏的玉米地”(容斥原理在程序设计上的典型应用)
“被破坏的玉米地”(容斥原理在程序设计上的典型应用),西电计算机学院2018年秋组合数学解决的问题。
1 题目描述
“被破坏的玉米地”:
“哈姆!外星人又在那儿了!”。埃塞和哈姆的玉米地是长方形的。每年在丰收之前,他们的玉米地都会很奇怪底遭到毁坏(据 埃塞说是外星人干的),所有破坏的地方都是以1米为半径的圆。哈姆发现如果在玉米地上建立一个适当的直角坐标系的话,那些圆心的坐标都为整数。万幸的是,埃塞和哈姆有玉米保险,但必须把毁坏的面积统计出来。
输入文件的第一行为一个整数n(0≤n≤200),表示圆圈的个数。以下n行每行有两个整数x和y,由空格分开,代表圆心坐标。
当两个圆的圆心坐标为(0,0)和(1,0)时 图3.1两个圆所覆盖的面积为5.0548。编写程序需要输出统计的总面积,四舍五入到小数点后四位。
1.2 应用“容斥原理”的思路
利用组合数学的“容斥原理”的方法,可以设计出更好的算法,从而达到更好的效果。
(一). 两个圆的交的情况(交集不为):
设圆的圆心坐标为(,),定义函数如下
在讨论两个圆的交的问题时,设两圆为圆1与圆2,它们的交集不为空集的情况有以下两种:
(1)
其交称为型交,如图所示。
设阴影部分的面积为S,则
(2)
其交称为型交,如图所示。
设阴影部分的面积为S,则
由于两个圆的非空交集的问题是最简单的问题。所以我们规定的交为型交,的交为型交,这个规定将在下面的讨论中用到。
(二 ). 三个圆的交的情况(交集不为):
经过分析易证:若三个圆的交集不为,则三个圆中任意两圆的交集一定不为空,反之亦成立。且在任意两圆相交所组成的三个交中,一定有2个型交,1个型交。如图所示,
设阴影部分的面积为,则有
(三). 四个圆的交的情况(交集不为)
经过分析可证:若四个圆的交集不为,则四个圆的圆心一定围成一个边厂为1的正方形。这四个圆心按照顺时针(或逆时针方向)形成4个型交,四个圆的交集如图阴影部分所示,
设其阴影部分的面积为,则
可以证明五个或五个以上互不重合的单位圆的交集必为。
分析至此,我们可以知道,任意多个单位圆的交集都可以通过两个、三个、四个圆相交的情况获得,呢么任意多个单位圆的并集呢?由交集到并集,这使我们想到了容斥原理,于是得出
其中,表示第个圆在平面直角坐标系中所占的区域。
2 代码实现
为了更好地展示最后的结果,我采用了文件输入输出的方法,坐标也是随机生成的,便于我们去进行统计和验证程序的准确性,以及多次分析程序的结果。
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <math.h>
#include <time.h>
#include <memory.h>
#define MAX 80
const double pi=3.14159265358979324;
int randmin=1,randmax=MAX;
using namespace std;
/**原点坐标**/
struct coordinate
{
int x;
int y;
};
/**计算任意两个坐标之间的距离”,这里的距离定义为横坐标之差与纵坐标之差的和**/
int f1(coordinate k1,coordinate k2)
{
int l;
if(abs(k1.x-k2.x)==0&&abs(k1.y-k2.y)==2){
return 0;
}
if(abs(k1.x-k2.x)==2&&abs(k1.y-k2.y)==0){
return 0;
}
l=abs(k1.x-k2.x)+abs(k1.y-k2.y);
if (l>2) return 0;
else return l;
}
/* 坐标随机生成*/
bool generate_rand(int min,int max,coordinate*a,int n)
{
int *sign;
int i=0;
FILE *fp;
if ((fp=fopen("coordinates.dat","wb+"))==NULL)
{
cout<<"connot open this file!"<<endl;
return false;
}
fprintf(fp,"%d个坐标分别为\n排序之前的坐标:\n",n);
sign=(int*)malloc(sizeof(int)*(max+1));
memset(sign,0,sizeof(int)*(max+1));
//srand((int)time(0));
for (i=0;i<n;i++)
{
a[i].x=min+(int)((double)(max-min)*rand()/(RAND_MAX+1.0));
a[i].y=min+(int)((double)(max-min)*rand()/(RAND_MAX+1.0));
if (sign[a[i].x]!=0)//确保任意两个坐标互不相同
{
if (a[sign[a[i].x]].y==a[i].y)
{
i--;
continue;
}
}else
{
sign[a[i].x]=i;
}
fprintf(fp,"(%d,%d)\n",a[i].x,a[i].y);
}
free(sign);
fclose(fp);
if (i==n)
return true;
else
return false;
}
/**快速排序算法的比较函数**/
int compare( const void *a, const void *b )
{
coordinate *arg1=(coordinate*)a;
coordinate *arg2=(coordinate*)b;
if (arg1->x<arg2->x)
return -1;
else if(arg1->x>arg2->x)
return 1;
else if (arg1->y<arg2->y)
return -1;
else if(arg1->y>arg2->y)
return 1;
else return 0;
};
int main()
{
int i,n;
double area;
coordinate *a;
double duration;
cout<<"请输入圆心的个数:"<<endl;
cin>>n;
//生成随机数
randmax=(n/2)<MAX ? (n/2):MAX;
a=(coordinate*)malloc(sizeof(coordinate)*n);
generate_rand(randmin,randmax,a,n);
qsort(a,n,sizeof(coordinate),compare);
FILE *fp;
if ((fp=fopen("coordinates.dat","ab+"))==NULL)
{
cout<<"connot open this file!"<<endl;
}
fprintf(fp,"\n随机生成的坐标排序之后:\n");
for (i=0;i<n;i++)
{
cout<<"("<<a[i].x<<","<<a[i].y<<")"<<endl;
fprintf(fp,"(%d,%d)\n",a[i].x,a[i].y);
}
int *List;
List=(int*)malloc(sizeof(int)*n*n);
int j,k,l,count1=0,count2=0,count3=0,count4=0;
area=n*pi;
//四种情况的相交面积
double s1=(2.0/3.0)*pi-sqrt(3.0)/2.0;
double s2=pi/2.0-1.0;
double s3=5.0/12.0*pi-sqrt(3.0)/2.0;
double s4=pi/3.0-1+0.25/(sin(75*pi/180)*sin(75*pi/180));
//分析两个圆相交的情况
for (i=0;i<n-1;i++)
for (j=i+1;j<n;j++)
{
*(List+n*i+j)=f1(a[i],a[j]);
*(List+n*j+i)=*(List+n*i+j);
if (*(List+n*i+j)==1)
count1++;
else if ((*(List+n*i+j)==2)&&(abs(a[i].x-a[j].x)==1))
count2++;
}
//分析三个圆或者四个圆相交的情况
for (i=0;i<n-2;i++)
for (j=i+1;j<n-1;j++)
for (k=j+1;k<n;k++)
{
bool check=true;
int ans=*(List+n*i+j)+*(List+n*j+k)+*(List+n*i+k);
if(*(List+n*i+j)==0||*(List+n*j+k)==0||*(List+n*i+k)!=0)
if(ans==4&&check)
{
count3++;
}
}
for (i=0;i<n;i++)
for (j=0;j<n;j++)
for (k=0;k<n;k++)
for(l=0;l<n;l++){
if ((*(List+n*i+j)==1)&&(*(List+n*j+k)==1)&&(*(List+n*k+l)==1)&&(*(List+n*i+l)==1)&&(i!=j)&&(i!=k)&&(i!=l)&&(j!=k)&&(j!=l)&&(k!=l))
count4++;
}
area=area-s1*count1-s2*count2+s3*count3-s4*count4/8;
cout<<"两个圆相横交的情况有"<<count1<<"种"<<endl;
cout<<"两个圆相斜交的情况有"<<count2<<"种"<<endl;
cout<<"三个圆相交的情况有"<<count3<<"种"<<endl;
cout<<"四个圆相交的情况有"<<count4/8<<"种"<<endl;
cout<<"总面积为"<<area<<endl;
fclose(fp);
free(a);
free(List);
}
3 实现结果