「PKUSC2018」题解

PKUSC都是神仙题,蒟蒻我根本做不来。

传送门

真实排名
最大前缀和
主斗地
星际穿越
神仙的游戏
PKUSC

真实排名

题解

这道题目还是比较简单的,就是有几个坑。

对于第$i$个人,有两种情况:他的成绩没有翻倍或者他的成绩翻倍了。

  1. 没有翻倍:

    那么分数在区间$[\lceil \frac{A_i}{2}\rceil,A_i-1]$内的人的分数不能翻倍,设除$i$外有$x$个人的分数不在这个区间内,那么此时的方案数为$\tbinom{x}{k}$。

  2. 翻倍了:

    那么分数在区间$[A_i,2\cdot A_i-1]$内的人的分数必须翻倍,设有$x$个人的分数在这个区间内,那么此时的方案数为$\tbinom{n-x}{k-x}$。

还有一种特殊情况需要处理:$A_i=0$,此时的方案数为$\tbinom{n}{k}$。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=100005,TT=998244353;
int n,K,A[maxn],B[maxn],fac[maxn],inv[maxn];
inline int read()
{
int ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=getchar();}
return ret*f;
}
inline int QP(int a,int b)
{
int ret=1,w=a;
while(b)
{
if(b&1) ret=(LL)ret*w%TT;
w=(LL)w*w%TT;b>>=1;
}
return ret;
}
inline int C(int n,int m){if(m<0)return 0;return (LL)fac[n]*inv[m]%TT*inv[n-m]%TT;}
inline int Count(int x){return upper_bound(B,B+n+1,x)-B-1;}
int main()
{
n=read();K=read();fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=(LL)fac[i-1]*i%TT;
inv[n]=QP(fac[n],TT-2);
for(int i=n-1;i>=0;i--) inv[i]=(LL)inv[i+1]*(i+1)%TT;
for(int i=1;i<=n;i++) A[i]=B[i]=read();
sort(B+1,B+1+n);B[0]=-1;
for(int i=1;i<=n;i++)
{
if(A[i]==0)
{
printf("%d\n",C(n,K));
continue;
}
int cntl=Count(A[i]-1)-Count((A[i]+1)/2-1)+1,ans=C(n-cntl,K);
int cntr=Count(2*A[i]-1)-Count(A[i]-1);ans=(ans+C(n-cntr,K-cntr))%TT;
printf("%d\n",ans);
}
return 0;
}

最大前缀和

题解

看到这个数据范围,状压DP肯定是跑不掉了。

一个数列可能有多个最大前缀和,方便起见,我们只关心包含元素个数最大的一个。

设$\sum{i=1}^{j}{a_i}$为最大前缀和,且$j$为所有最大的前缀和中最大的,那么就有$\forall k\in[j+1,n],\sum{i=j+1}^{k}{a_i}<0$。

令$U$为由原序列中所有元素组成的集合,$S$为$U$的一个子集,$F[S]$表示由$S$中所有元素构成的排列中,最大前缀和包含了$S$中所有元素的排列的数量,$G[S]$表示任意一个前缀和都严格小于$0$的排列的数量,$Sum[S]$表示由$S$中所有元素的加和。

如果已经求出了$F$和$G$,那么不难得到答案:

考虑如何求$F$和$G$,假设已经求得了$F[S]$和$G[S]$,考虑如何转移。

枚举$i\notin S$,然后把$i$放到$S$的排列的最前面,如果如果此时$Sum[S]\geq 0$,那么就有转移:

考虑把$i$放在最后面,如果有$a_i+Sum[S]<0$,那么就有转移:

由于一个前缀和至少包含一个元素,所以就有初始值$F[S]=[|S|==1]$。

由于当一个前缀和包含了所有元素之后,最大前缀和之后就没有元素了,所以就有初始值$G[0]=1$。

然后这题就解完了,时间复杂度$O(n\cdot 2^n)$。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<cstdio>
using namespace std;
typedef long long LL;
const int maxn=25,TT=998244353;
int n,a[maxn],sum[(1<<20)+5],F[(1<<20)+5],G[(1<<20)+5],ans;
inline void Inc(int& x,int y){x=(x+y>=TT?x+y-TT:x+y);}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int S=0;S<(1<<n);S++)
for(int j=1;j<=n;j++)
if(S&(1<<(j-1)))
{sum[S]=sum[S-(1<<(j-1))]+a[j];break;}
G[0]=1;
for(int i=1;i<=n;i++) F[1<<(i-1)]=1;
for(int S=0;S<(1<<n);S++)
{
for(int j=1;j<=n;j++)
{
if(!(S&(1<<(j-1))))
{
if(sum[S]>=0) Inc(F[S|(1<<(j-1))],F[S]);
if(sum[S]+a[j]<0) Inc(G[S|(1<<(j-1))],G[S]);
}
}
}
for(int S=0;S<(1<<n);S++)
Inc(ans,(LL)F[S]*G[(1<<n)-1-S]%TT*(sum[S]+TT)%TT);
printf("%d\n",ans);
return 0;
}

主斗地

题解

大模拟。

仔细阅读题目中的规则,不难发现,在这题中很多打法是无用的,顺子、对子、双顺、三张、三顺全都可以拆成单牌来打;飞机带翅膀拆成三带一和三带二打,并且还少了要求数码不同的限制。这样我们需要考虑的出牌方式就只有三带一、三带二、四带二和打单牌了,这对一道大模拟来说真是莫大的好消息。

并且良心出题人把3去掉了,因此吉老师的牌的可能的种类就大大减少了,大概是$10^6$级别。

所以可以暴枚吉老师的牌,然后再考虑,对于一副吉老师的牌,是否存在合法的打法。

可以一开始先确定三带一加上三带二的数量和四带二的数量,最后再枚举三带一的数量以及考虑怎么带(因为对于一个玩家来说,我们只关心三带一的数量和三带二的数量,而并不关心具体是哪些三张牌带了一张,哪些三张牌带了两张)。

对于吉老师,肯定是尽量带大的散牌,对于XX网友,肯定是尽量带小的牌。并且需要先确定带哪些对子,再确定带哪些单牌。

具体实现参加代码。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include<cstdio>
#include<cctype>
#include<cstring>
using namespace std;
const int maxn=20;const char C[maxn]="3456789TJQKA2wW";
int XX[maxn],lft[maxn],Xcnt[maxn],ans,Jcnt[maxn];
inline int Get()
{
char ch=getchar();
while(!isalpha(ch)&&!isdigit(ch)) ch=getchar();
for(int i=1;;i++) if(ch==C[i]) return i;
return -1;
}
inline bool CheckB(int dy,int dye)
{
static int tepX[maxn],tepJ[maxn];
for(int i=0;i<=dye;i++) //枚举带一和带二的数量
{
memcpy(tepX,Xcnt,sizeof(tepX));
memcpy(tepJ,Jcnt,sizeof(tepJ));
int bri=dy+i,pai=dye-i;
for(int j=14;j;j--) //吉老师带大牌
{
while(pai&&tepJ[j]>=2){tepJ[j]-=2;pai--;}
while(bri&&tepJ[j]>=1){tepJ[j]-=1;bri--;}
}
if(bri>0||pai>0) continue;
bri=dy+i;pai=dye-i;
for(int j=1;j<=14;j++) //XX网友带小牌
{
while(pai&&tepX[j]>=2){tepX[j]-=2;pai--;}
while(bri&&tepX[j]>=1){tepX[j]-=1;bri--;}
}
if(bri>0||pai>0) continue;
bool suc=true;
for(int j=1,p=1;j<=14&&suc;j++) //剩下的牌一定要严格的一张压一张
{
while(tepJ[j]--)
{
while(p<=14&&(p<=j||!tepX[p])) p++;
if(p>14){suc=false;break;}
tepX[p]--;
}
}
if(suc) return true;
}
return false;
}
inline bool check(int s,int sa,int si,int dy,int dye) //暴枚三张和四张的打法
{
if(s>14)
{
if(sa||si) return false; //两人打的三张和四张的数量必须一样
return CheckB(dy,dye);
}
if(Jcnt[s]>=3)
{
Jcnt[s]-=3;
bool ret=check(s+1,sa+1,si,dy,dye+1);
Jcnt[s]+=3;
if(ret) return true;
}
if(Jcnt[s]>=4)
{
Jcnt[s]-=4;
bool ret=check(s+1,sa,si+1,dy+2,dye);
Jcnt[s]+=4;
if(ret) return true;
}
if(Xcnt[s]>=3&&sa) //XX网友的牌要压着吉老师打
{
Xcnt[s]-=3;
bool ret=check(s+1,sa-1,si,dy,dye);
Xcnt[s]+=3;
if(ret) return true;
}
if(Xcnt[s]>=4&&si)
{
Xcnt[s]-=4;
bool ret=check(s+1,sa,si-1,dy,dye);
Xcnt[s]+=4;
if(ret) return true;
}
return check(s+1,sa,si,dy,dye);
}
void DFS(int step,int s) //暴枚吉老师的牌
{
if(step>17)
{
if(check(1,0,0,0,0)) ans++;
return;
}
for(int i=s;i<=14;i++)
{
if(lft[i])
{
lft[i]--;
Jcnt[i]++;
DFS(step+1,i);
Jcnt[i]--;
lft[i]++;
}
}
}
int main()
{
for(int i=1;i<=12;i++) lft[i]=4;
lft[13]=lft[14]=1;
for(int i=1;i<=17;i++){XX[i]=Get();lft[XX[i]]--;Xcnt[XX[i]]++;}
DFS(1,1);
printf("%d\n",ans);
return 0;
}

星际穿越

题解

先考虑这个图有什么性质,以及最短路径有什么特点。

不难发现,肯定存在一条最短路径,向右跳了最多一次,而且是在起点向右跳。

令$tl[i]=\min_{j=i}^{n}l[j]$,如果$i$不是起点,那么区间$[tl[i],i-1]$中的所有点均可以看作可以由点$i$一次穿越到达(实际上可能并未经过点$i$,但是穿越次数和看为从$i$一次穿越到达一样),这样转化之后,每次都向左穿越到此意义下最靠左的位置肯定可以得到一条最短路径(起点除外)。

考虑倍增,设$F[i][j]$表示从$i$开始向左穿越$2^j$次能到达的最靠左的位置,$G[i][j]$表示从$i$出发到区间$[F[i][j],i]$中所有位置的最短路径长度和。

对于$F[i][j]$和$G[i][j]$,有:

然后对于每一个询问,差分一下然后倍增即可求解出答案。倍增的时候每次跳跃记得预先加上前面那部分的贡献。注意起点特殊处理。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=300005;
int n,q,l[maxn],tl[maxn];LL F[maxn][20],G[maxn][20];
inline int read()
{
int ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=getchar();}
return ret*f;
}
inline LL gcd(LL x,LL y){return !y?x:gcd(y,x%y);}
inline LL Calc(int X,int L)
{
LL ret=0;int t=X;
for(int i=18;i>=0;i--)
if(F[t][i]>=L)
{ret+=G[t][i];t=F[t][i];ret+=(t-L)*(1<<i);}
return ret+t-L;
}
inline LL Solve(int X,int L)
{
if(L>=l[X]) return X-L;
return Calc(l[X],L)+X-L;
}
int main()
{
n=read();
for(int i=2;i<=n;i++) l[i]=read();
tl[n]=l[n];
for(int i=n-1;i;i--)
tl[i]=min(tl[i+1],l[i]);
q=read();
for(int i=1;i<=n;i++)
{
F[i][0]=tl[i];
G[i][0]=i-tl[i];
for(int j=1;j<=18;j++)
{
F[i][j]=F[F[i][j-1]][j-1];
G[i][j]=G[i][j-1]+((LL)F[i][j-1]-F[i][j])*(1<<(j-1))+G[F[i][j-1]][j-1];
}
}
while(q--)
{
int L=read(),R=read(),X=read();
LL u=Solve(X,L)-Solve(X,R+1),d=R-L+1,g=gcd(u,d);
u/=g;d/=g;printf("%lld/%lld\n",u,d);
}
return 0;
}

神仙的游戏

题解

神仙的游戏蒟蒻我是根本玩不来。

首先需要考虑一下这个题目有什么性质:

自己画几个图之后不难发现,对于一个长度为$len$的前缀,如果他是一个border,那么这个$n-len$必定为这个字符串循环节的长度,并且反过来也成立。

因为?可以随便取,这里就不管它了。我们只需要考虑哪些情况下的,长度为len的前缀不是border。

对于一个$0$,设它的下标为$i$,对于一个$1$,设它的下标为$j$,那么就有:

长度为$len$的前缀不是一个border(中间的竖线表示整除)。

考虑构造两个多项式:

然后用FFT或者NTT将两个多项式卷乘起来,设卷积的结果为$H()$。

此时如果存在一对01,且它们的下标之差为$i$,那么就等价于$[x^{n+1-i}]H(x)+[x^{n+1+i}]H(x)>0$。

此时就可以判断一个长度为$len$是否是一个border了。

然后这题就解决了,复杂度$O(n\log n)$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=500005,maxl=(1<<20)+5,TT=998244353;
int n,f[maxn],A[maxl],B[maxl],r[maxl];char S[maxn];LL ans;
inline int QP(int a,int b)
{
int ret=1,w=a;
while(b)
{
if(b&1) ret=(LL)ret*w%TT;
w=(LL)w*w%TT;b>>=1;
}
return ret;
}
inline void NTT(int* A,int limit,int typ)
{
for(int i=0;i<limit;i++)
if(i<r[i])
swap(A[i],A[r[i]]);
for(int mid=1;mid<limit;mid<<=1)
{
int gn=QP(3,(TT-1)/(mid<<1));
if(typ<0) gn=QP(gn,TT-2);
for(int j=0;j<limit;j+=mid<<1)
{
int g=1;
for(int k=0;k<mid;k++,g=(LL)g*gn%TT)
{
int x=A[j+k],y=(LL)g*A[j+k+mid]%TT;
A[j+k]=(x+y)%TT;
A[j+k+mid]=(x-y+TT)%TT;
}
}
}
if(typ<0)
{
int inv=QP(limit,TT-2);
for(int i=0;i<limit;i++) A[i]=(LL)A[i]*inv%TT;
}
}
inline void Solve()
{
memset(A,0,sizeof(A));
memset(B,0,sizeof(B));
for(int i=1;i<=n;i++)
{
A[i]=(S[i]-'0'==1);
B[i]=(S[n-i+1]-'0'==0);
}
int limit=1,l=0;
while(limit<=2*n){limit<<=1;l++;}
for(int i=0;i<limit;i++)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
NTT(A,limit,1);NTT(B,limit,1);
for(int i=0;i<limit;i++) A[i]=(LL)A[i]*B[i]%TT;
NTT(A,limit,-1);
for(int i=1;i<n;i++)
if(A[n+1+i]||A[n+1-i])
for(int j=1;j*j<=i;j++)
if(i%j==0)
f[n-i/j]=f[n-j]=0;
}
int main()
{
gets(S+1);n=strlen(S+1);
for(int i=1;i<=n;i++) f[i]=1;
Solve();
for(int i=1;i<=n;i++)
ans=ans^((LL)f[i]*i*i);
printf("%lld\n",ans);
return 0;
}

PKUSC

题解

计算几何,好讨厌啊。

虽然题面里保证了精度不会卡得很严,比较友好,但是作为一道计算几何,精度问题还是很讨厌的,特别是对于我这种蒟蒻。

由于期望具有线性性,所以可以分开计算每个敌人的贡献,最后再累加起来。

一个敌人的贡献等于多边形匀速转过一圈时,敌人在多边形内的时间与总时间的比值。

考虑如何判断一个点是否在一个可能是凹多边形的多边形内部,我这里用的是引射线法:从需要判断的点引出一条射线,计算这条射线与多边形的边的相交次数。如果相交了奇数次,那么就在多边形内部;如果相交了偶数次,那么就在多边形外部。

然后对于每一个点,计算出它与多边形的每一条边是否可能相交,以及相交时转过的角度。注意在本题中,转多边形和转点本质上是一样的,计算交点的话解一个一元二次方程即可。我们称转过的角度$\alpha$为关键角度当前仅当在转过$\alpha$之后,该点与多边形的某一条边相交。

预处理出所有关键角度并排序之后(注意去重),不难发现当转过角度在某两个相邻的关键角度之间时,该点在多边形的内外情况相同。

所以对于每一个点,我们需要计算出他的所有关键角度,并确定旋转角度在某一个区间内时该点是在多边形内部还是外部,然后累加计算答案。时间复杂度为$O(nm^2)$。

代码

注意由于本蒟蒻很菜,并不怎么会处理计算几何里的精度问题,所以下面的代码仅保证基本思路正确且能通过洛谷、BZOJ以及LOJ的测试数据,但是不保证不存在数据能通过精度问题把代码卡掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn=205,maxm=505;const double eps=1e-9,Pi=acos(-1);
int n,m;double ans;
inline int read()
{
int ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=getchar();}
return ret*f;
}
inline double ABS(double x){return x<0?-x:x;}
struct Point
{
double x,y,r;
Point(double a=0,double b=0){x=a;y=b;}
inline double Angle() //计算一个点的极角大小
{
double t=asin(y/sqrt(x*x+y*y));
if(t>0)
{
if(x>0&&y>0) return t;
return Pi-t;
}
if(x<0&&y<0) return Pi-t;
return 2*Pi+t;
}
}P[maxm],E[maxn];
struct Line
{
double k,b,x;bool inf;Point A,B;
inline void Init(Point p1,Point p2) //根据两个端点确定解析式
{
A=p1;B=p2;
if(ABS(p1.x-p2.x)<eps){inf=true;x=p1.x;k=b=0;} //线是竖着的
else{k=(p2.y-p1.y)/(p2.x-p1.x);b=p1.y-k*p1.x;inf=false;x=0;}
}
}L[maxm];
inline bool AtEdge(Point p,Line L,bool typ=-1) //判断一个点是否在一条线段上
{
double eps=1e-6;
if(L.inf)
{
if(ABS(p.x-L.x)<eps&&min(L.A.y,L.B.y)-eps<=p.y&&p.y<=max(L.A.y,L.B.y)+eps)
return true;
}
else
{
if(ABS(p.y-L.k*p.x-L.b)<eps&&min(L.A.x,L.B.x)-eps<=p.x&&p.x<=max(L.A.x,L.B.x)+eps)
return true;
}
return false;
}
inline bool check(Point p)
{
for(int i=1;i<=m;i++)
if(AtEdge(p,L[i],1))
return true;
return false;
}
inline bool InOrOut(Point p) //判断一个点是否在多边形内部
{
while(true)
{
double k=(double)rand()/RAND_MAX;//ran一个射线的斜率,尽量避免与线段的端点相交
if(k<1e-6) continue; //防止斜率过小或者过大带来精度问题
if(rand()&1) k=1/k;
double b=p.y-p.x*k;
bool fal=false;
for(int i=1;i<=m;i++)
if(ABS(L[i].A.x*k+b-L[i].A.y)<eps||ABS(L[i].B.x*k+b-L[i].B.y)<eps)
{fal=true;break;}
if(fal) continue;
int cnt=0;
for(int i=1;i<=m;i++)
{
Point cp;
if(L[i].inf)
{
cp.x=L[i].x;cp.y=cp.x*k+b;
if(p.x-eps<=cp.x&&AtEdge(cp,L[i])) cnt++;
}
else
{
cp.x=(L[i].b-b)/(k-L[i].k);cp.y=k*cp.x+b;
if(p.x-eps<=cp.x&&AtEdge(cp,L[i])) cnt++;
}
}
return cnt&1;
}
}
inline void Solve(Point p) //计算每个点的贡献
{
static double an[maxm*2],ap,aq[maxn*2];
int len=0,cnt=0;ap=p.Angle();
for(int i=1;i<=m;i++)
{
Point cp1,cp2;
if(L[i].inf)
{
double t=p.r*p.r-L[i].x*L[i].x;
if(t<0) continue;
cp1.x=cp2.x=L[i].x;
cp1.y=sqrt(t);cp2.y=-sqrt(t);
}
else
{
double a=L[i].k*L[i].k+1,b=2*L[i].k*L[i].b,c=L[i].b*L[i].b-p.r*p.r,delta=b*b-4*a*c;
if(delta+eps<0) continue;
if(delta<0) delta=0;
delta=sqrt(delta);
cp1.x=(-b+delta)/(2*a);cp1.y=cp1.x*L[i].k+L[i].b;
cp2.x=(-b-delta)/(2*a);cp2.y=cp2.x*L[i].k+L[i].b;
}
if(AtEdge(cp1,L[i]))
{
an[++len]=cp1.Angle()-ap;
if(an[len]<0) an[len]+=2*Pi;
}
if(AtEdge(cp2,L[i]))
{
an[++len]=cp2.Angle()-ap;
if(an[len]<0) an[len]+=2*Pi;
}
}
sort(an+1,an+1+len);
an[++len]=2*Pi;
for(int i=1;i<=len;)
{
int j=i;
while(ABS(an[j+1]-an[i])<eps&&j<len) j++;
aq[++cnt]=an[i];i=j+1;
}
for(int i=1;i<=cnt;i++)
{
double ant=ap+aq[i]-1e-6;Point tp;
tp.x=p.r*cos(ant);tp.y=p.r*sin(ant);
ans+=InOrOut(tp)*(aq[i]-aq[i-1])/(2*Pi);
}
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
E[i].x=read();
E[i].y=read();
E[i].r=sqrt(E[i].x*E[i].x+E[i].y*E[i].y);
}
for(int i=1;i<=m;i++)
{
P[i].x=read();
P[i].y=read();
}
for(int i=1;i<m;i++)
L[i].Init(P[i],P[i+1]);
L[m].Init(P[m],P[1]);
for(int i=1;i<=n;i++)
{
if(E[i].r<eps) //敌人在原点上,转不动
{
if(!check(E[i])) ans+=InOrOut(E[i]);
swap(E[i],E[n]);n--;i--;
}
}
for(int i=1;i<=n;i++) Solve(E[i]);
printf("%.5lf\n",ans);
return 0;
}