题目
https://gmoj.net/senior/#main/show/3976
题解
这道题目有不少解题的方法,但是大多比较麻烦。这里提供一种简捷的方法(改进自 x i a o q i xiaoqi xiaoqi大佬的解法)
为了方便,不妨先将所有点平移至第一象限。假设要求出的是简单多边形ABCDE中的青蛙贡献值,各顶点分布如下图所示:
令 n u m △ A B C num_{△ABC} num△ABC表示△ABC中青蛙的价值之和。
发现这是等于 n u m △ O A B + n u m △ O B C + n u m △ O C D − n u m △ O E D − n u m △ O A E num_{△OAB}+num_{△OBC}+num_{△OCD}-num_{△OED}-num_{△OAE} num△OAB+num△OBC+num△OCD−num△OED−num△OAE。不难得出结论:
多边形的num=Σ多边形中的 某些边 与点O所形成的三角形的num-Σ多边形中的 其它边 与点O所形成的三角形的num
我们不妨把这些点极角排序,接着顺时针(或逆时针)扫这些边。假如现在扫到边 A i A i + 1 A_iA_{i+1} AiAi+1,如果 A i A_i Ai的极角序大于 A i + 1 A_{i+1} Ai+1的极角序,那么就加上这条边与O形成三角形的num,否则减去这条边的num。这样子结果的绝对值等于答案。
现在问题就是怎么快速求出图中任意一个三角形的num。
考虑割补法,发现对于一个三角形ABO(如下图), n u m △ A B O = n u m △ A O A ′ − n u m △ B O A ′ num_{△ABO}=num_{△AOA'}-num_{△BOA'} num△ABO=num△AOA′−num△BOA′(A’是AB的延长线与x轴的交点)。
我们以每个顶点为原点,给所有的青蛙点、顶点以及原点极角排序(这里可以以第三象限最小、第二象限最大的极角值来排序),然后维护一个前缀和数组 s u m i , j sum_{i,j} sumi,j记录以点 i 为原点,对其他点进行极角排序,其中 j 以及极角值比 j 的要小的点的价值和(这里不妨把顶点和原点视作价值为0的青蛙)。
由于题目保证了无三点共线的情况,而有两点与原点共线的情况又很好处理(只用让原点的极角序比与它极角值相等的点的极角序小就行了),这样就能快速求出 n u m △ A O A ′ num_{△AOA'} num△AOA′了(其实就是 s u m A , B − s u m A , O sum_{A,B}-sum_{A,O} sumA,B−sumA,O 。但是 n u m △ B O A ′ num_{△BOA'} num△BOA′怎么求呢?可以在以顶点 A i A_i Ai为原点时,对于每一个顶点 A j ( i ≠ j ) A_j(i\not=j) Aj(i=j),都新建一个 A j A_j Aj关于 A i A_i Ai对称的点 A j ′ A_j' Aj′,然后和上面一样处理就行了。
时间复杂度 O ( n log ( n + m ) + ∑ s ) O(n\log(n+m)+\sum s) O(nlog(n+m)+∑s)。
x i a o q i xiaoqi xiaoqi大佬在求一个三角形的num时做法不同,他是把以每一个顶点为原点给其它点排好序,算出前缀和。然后询问时用余弦定理算出∠A’OB,二分求价值和。
CODE
#include<algorithm>
#include<cstdio>
#include<cmath>
using namespace std;
#define llf long double
#define M 10001
#define N 1005
struct node{
int x,y,id,type;long double ang;}a[N],b[N],c[3005];
int n,v[N],arr[N],sum[N][N],_sum[N][N],edge[N][N];
inline char gc()
{
static char buf[100005],*l=buf,*r=buf;
return l==r&&(r=(l=buf)+fread(buf,1,100005,stdin),l==r)?EOF:*l++;
}
inline void read(int &k)
{
char ch;bool sign=0;
while(ch=gc(),ch!='-'&&(ch<'0'||ch>'9'));
if(ch=='-') sign=1,ch=gc();k=ch-'0';
while(ch=gc(),ch>='0'&&ch<='9') k=k*10+ch-'0';
if(sign) k=-k;
}
inline int abs(int x,int y){
return x<0?-x:x;}
inline bool cmp(node x,node y)
{
return x.ang!=y.ang?x.ang<y.ang:abs(x.x)>abs(y.x);}
inline int calc(int x,int y)
{
int sign=cmp(a[x],a[y])?1:-1;
if(sign>0) x^=y,y^=x,x^=y;
return sign*(sum[x][y]+sum[y][n+1]-sum[x][n+1]-_sum[y][x]);
}
int main()
{
int m,q,k,s,cnt;
read(n),read(m);
for(int i=1;i<=n;++i) read(a[i].x),read(a[i].y),a[i].ang=atan2((llf)a[i].y,(llf)a[i].x);
for(int i=1;i<=m;++i) read(b[i].x),read(b[i].y),read(v[i]),b[i].ang=atan2((llf)b[i].y,(llf)b[i].x);
for(int i=1;i<=n;++i) a[i].x+=M,a[i].y+=M;
for(int i=1;i<=m;++i) b[i].x+=M,b[i].y+=M;
for(int i=1;i<=n;++i)
{
s=1,cnt=0,c[1]=(node){
-a[i].x,-a[i].y,n+1,0};
for(int j=1;j<=n;++j) if(i^j)
c[++s]=(node){
a[j].x-a[i].x,a[j].y-a[i].y,j,0,atan2((llf)(a[j].y-a[i].y),(llf)(a[j].x-a[i].x))},
c[++s]=(node){
a[i].x-a[j].x,a[i].y-a[j].y,j,1,atan2((llf)(a[i].y-a[j].y),(llf)(a[i].x-a[j].x))};
for(int j=1;j<=m;++j) c[++s]=(node){
b[j].x-a[i].x,b[j].y-a[i].y,j,2,atan2((llf)(b[j].y-a[i].y),(llf)(b[j].x-a[i].x))};
sort(c+1,c+s+1,cmp);
for(int j=1;j<=s;++j)
{
if(!c[j].type) sum[i][c[j].id]=cnt;
else if(c[j].type==1) _sum[i][c[j].id]=cnt;
else cnt+=v[c[j].id];
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<i;++j)
edge[i][j]=calc(i,j),edge[j][i]=-edge[i][j];
read(q);
for(int p=1;p<=q;++p)
{
read(k);
for(int i=1;i<=k;++i) read(arr[i]);
cnt=edge[arr[k]][arr[1]];
for(int i=1;i<k;++i) cnt+=edge[arr[i]][arr[i+1]];
printf("%d\n",abs(cnt));
}
return 0;
}