题目链接:https://ac.nowcoder.com/acm/contest/882/G

题目大意:有\(n\)条直线将平面分成若干个区域,要求处理\(m\)次询问:求第\(q\)大的区域面积。保证没有三线共点或者两直线重合,\(n\leq 1000,\ m\leq 10000\)

题解:先考虑最多会有多少个区域,由于第\(i\)条直线最多与前面的\(i-1\)条直线同时相交,所以交点个数和区域个数都是\(n^2\)级别的,考虑求出所有区域的面积并排序

   首先预处理所有的交点,并记录与该交点相邻的交点,在他们之间建两条有向边(即记录两个向量)。由于没有三线共点的情况,所以每个点的度数均不超过\(4\)。每次选取一个点作为起点,并选取另一个与之相邻的点作为当前点,从当前点出发寻找一个没被使用过且与当前边的向量夹角最大(或最小)的边,并进入下一个点,直至回到原点。这样就能找出其中的一个区域。执行这样的算法若干次直至所有边均被使用过就能求出所有的区域面积,注意判断当前区域不合法的情况即可。

   关于为什么每次找夹角最大(或最小)的边能找到所有区域,是因为这个过程实际上是平面图转对偶图的算法实现(具体见http://blog.miskcoo.com/2015/05/planar-graph-dual-and-point-locate 这篇博客的讲解,感谢qls深夜给我普及这一知识点),因此如果每次找夹角最小的边是显然正确的。关于找夹角最大的正确性我想是因为,由于每个交点最多是两条直线的交点,所以最小角和最大角的区别就是左转和右转, 也是满足这一算法的要求的。

   对于这题的代码实现,我的做法是对于每条直线先预处理所有交点坐标并排好序(可以用点乘实现),然后就可以处理出和每个交点相邻的点是哪几个。由于每次只可能有两个合法的相邻点,所以可以直接用叉积的正负来判断下一个要走哪个点,剩下的就是判断合法性的问题了

 #include<bits/stdc++.h>
using namespace std;
#define N 1001
#define mp make_pair
const double eps=1e-;
int n,m,q,cnt,f[N*N],poly[N];
vector<pair<int,bool>>d[N*N];
vector<pair<double,int>>l[N];
vector<double>ans;
struct Point
{
double x,y;
void read(){scanf("%lf%lf",&x,&y);}
Point operator +(const Point &t)const{return {x+t.x,y+t.y};}
Point operator -(const Point &t)const{return {x-t.x,y-t.y};}
double operator *(const Point &t)const{return x*t.y-y*t.x;}
double operator %(const Point &t)const{return x*t.x+y*t.y;}
Point operator *(double t)const{return {x*t,y*t};}
Point operator /(double t)const{return {x/t,y/t};}
}p[N*N];
struct Line
{
Point p1,p2;
void read(){p1.read(),p2.read();}
bool intersect(const Line &t)const
{return fabs((p2-p1)*(t.p2-t.p1))>eps;}
Point isct(const Line &t)const
{
double a=(p2-p1)*(t.p1-p1);
double b=(p2-p1)*(p1-t.p2);
return (t.p1*b+t.p2*a)/(a+b);
}
}a[N];
void del(int st,int ed)
{
for(int i=;i<d[st].size();i++)
if(d[st][i].first==ed)
{
d[st][i].second=false;
f[st]--;
return;
}
}
int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
a[i].read();
for(int i=;i<=n;i++)
for(int j=i+;j<=n;j++)
if(a[i].intersect(a[j]))
{
p[++cnt]=a[i].isct(a[j]);
l[i].push_back(mp((p[cnt]-a[i].p1)%(a[i].p2-a[i].p1),cnt));
l[j].push_back(mp((p[cnt]-a[j].p1)%(a[j].p2-a[j].p1),cnt));
}
for(int i=;i<=n;i++)
{
sort(l[i].begin(),l[i].end());
for(int j=;j<l[i].size();j++)
{
d[l[i][j].second].push_back(mp(l[i][j-].second,true));
d[l[i][j-].second].push_back(mp(l[i][j].second,true));
f[l[i][j].second]++;
f[l[i][j-].second]++;
}
}
for(int i=;i<=cnt;i++)
while(f[i])
{
int pcnt=,st=i,pre=i,cur=-;
poly[++pcnt]=st;
for(auto pi:d[st])
if(pi.second)
{
cur=poly[++pcnt]=pi.first;
break;
}
while(cur!=st)
{
int id=-;double mx=-1.0;
for(auto pi:d[cur])
if(pi.second)
{
int nxt=pi.first;
if(nxt==pre)continue;
double tmp=(p[cur]-p[pre])*(p[nxt]-p[cur]);
if(tmp>mx)mx=tmp,id=nxt;
}
if(id< || mx<eps)
{
for(int i=;i<pcnt;i++)
del(poly[i],poly[i+]);
pcnt=;
break;
}
poly[++pcnt]=id;
pre=cur,cur=id;
}
if(!pcnt)continue;
double res=;
for(int i=;i<=pcnt;i++)
{
res+=p[poly[i]]*p[poly[i%pcnt+]];
del(poly[i],poly[i%pcnt+]);
}
res*=0.5;
ans.push_back(fabs(res));
}
sort(ans.begin(),ans.end());
int sz=ans.size();
printf("%d %.6f %.6f\n",sz,ans[sz-],ans[]);
scanf("%d",&m);
while(m--)
{
scanf("%d",&q);
if(q>sz){printf("Invalid question\n");continue;}
printf("%.6f\n",ans[sz-q]);
}
}

[2019牛客多校第二场][G. Polygons]的更多相关文章

  1. 2019牛客多校第二场 A Eddy Walker(概率推公式)

    2019牛客多校第二场 A Eddy Walker(概率推公式) 传送门:https://ac.nowcoder.com/acm/contest/882/A 题意: 给你一个长度为n的环,标号从0~n ...

  2. 牛客多校第二场 G transform

    链接:https://www.nowcoder.com/acm/contest/140/G White Cloud placed n containers in sequence on a axes. ...

  3. 2019牛客多校第二场H-Second Large Rectangle

    Second Large Rectangle 题目传送门 解题思路 先求出每个点上的高,再利用单调栈分别求出每个点左右两边第一个高小于自己的位置,从而而得出最后一个大于等于自己的位置,进而求出自己的位 ...

  4. 2019 牛客多校第二场 H Second Large Rectangle

    题目链接:https://ac.nowcoder.com/acm/contest/882/H 题目大意 给定一个 n * m 的 01 矩阵,求其中第二大的子矩阵,子矩阵元素必须全部为 1.输出其大小 ...

  5. 2019牛客多校第二场H题(悬线法)

    把以前的题补补,用悬线求面积第二大的子矩形.我们先求出最大子矩阵的面积,并记录其行三个方向上的悬线长度.然后排除这个矩形,记得还得特判少一行或者少一列的情况 #include <bits/std ...

  6. 2019牛客多校第二场D-Kth Minimum Clique

    Kth Minimum Clique 题目传送门 解题思路 我们可以从没有点开始,把点一个一个放进去,先把放入一个点的情况都存进按照权值排序的优先队列,每次在新出队的集合里增加一个新的点,为了避免重复 ...

  7. 2019牛客多校第二场F-Partition problem(搜索+剪枝)

    Partition problem 题目传送门 解题思路 假设当前两队的对抗值为s,如果把红队中的一个人a分配到白队,s+= a对红队中所有人的对抗值,s-= a对白队中所有人的对抗值.所以我们可以先 ...

  8. 2019牛客多校第二场BEddy Walker 2——BM递推

    题意 从数字 $0$ 除法,每次向前走 $i$ 步,$i$ 是 $1 \sim K$ 中等概率随机的一个数,也就是说概率都是 $\frac{1}{K}$.求落在过数字 $N$ 额概率,$N=-1$ 表 ...

  9. [2019牛客多校第二场][A. Eddy Walker]

    题目链接:https://ac.nowcoder.com/acm/contest/882/A 题目大意:圆上有\(n\)个点,标号从\(0\)到\(n-1\),初始一个人在点\(0\),每次会等概率向 ...

随机推荐

  1. lua数据类型的的操作(三)

    上一章我们学习了lua的数据类型,以及语法的定义,今天我们学习lua的数据类型操作,其实就是lua库一些api的操作,遇到对数据类型处理时,可以根据lua库提供的操作来实现. 一.字符串操作 1.字符 ...

  2. 关于quartz定期,起服务时不新增配置文件中的定期问题

    关于quartz定期,起服务时不新增配置文件中的定期问题 问题原因:生产环境中起服务,未加载配置文件信息,且quartz连接超时 查找原因发现 由于别人新建了一个定期文件 并将 quartz工厂类的i ...

  3. Redis 学习笔记(篇八):事件

    Redis 服务器是一个事件驱动程序,服务器需要处理以下两类事件: 文件事件: Redis 服务器通过套接字与客户端(或者其他 Redis 服务器)进行连接,而文件事件就是服务器对套接字操作的抽象.服 ...

  4. npm添加代理和取消代理

    1.设置http代理 npm config set proxy=http://代理服务器地址:8080 2.取消代理 npm config delete proxy 3.npm设置淘宝镜像 npm c ...

  5. springboot中配置文件使用1

    1.表达方式:application.properties或者application.yml,这是已经约定成俗的文件,不用修改文件名,此文件为全局配置文件. 2.语法格式:yml或者yaml. a.基 ...

  6. Ural 1238 Folding 题解

    目录 Ural 1238 Folding 题解 题意 题解 程序 Ural 1238 Folding 题解 题意 定义折叠.展开为: 单个大写英文字母是一个折叠的串,把它展开后是它本身. 如果\(S\ ...

  7. shell习题第14题:

    [题目要求] 需求,根据web服务器的访问日志,把一些请求高的ip给拒绝掉,并且每隔半小时把不再发起请求或者请求量很小的ip给解封 假设: 1. 一分钟内请求量高于100次的ip视为不正常的请求 2. ...

  8. 选项卡TAB

    一.基础信息 关键class名:nav 写法: (1)头部选中状态:class="active" (2)头部按钮进行切换:<a>加data-toggle="t ...

  9. Java装饰者模式(思维导图)

    图1 装饰者模式[点击查看图片] 1,一个简单的以人为主体的装饰者模式 被装饰者 public interface Human {//被装饰者 public void wearClothes(); p ...

  10. java lesson10homework

    1.    输入三个整数x, y, z,请把这三个数由小到大输出. 2.  package Homework10; 3.  //:类方法 4.  public class Sort { 5.  voi ...