给定 \(n\) 个形如 \(x^ay^b\) 的项,Ani 先删去至多一项,Borna 再填正整数系数,当多项式有下界时 Borna 赢,否则 Ani 赢,求谁赢。
\(n\le 2\cdot 10^5\),\(0\le a,b\le 10^9\)。
称一堆单项式有下界当且仅当可以填正整数系数,使得该多项式有下界。
题目实际上是给定一堆单项式,求是否可以删去至多一项使得没有下界。
引理:一堆单项式有下界 \(\Leftrightarrow\) 将每一项 \(x^ay^b\rightarrow(a,b)\) 画在平面上,带上原点 \((0,0)\) 组成的点集的凸包的所有顶点的横纵坐标均为偶数。
证明:先考虑 \(\Leftarrow\),设 \(P_1,P_2,\cdots,P_m\) 是凸包顶点,则对于任意凸包内部整点 \(Q(a,b)\),存在有理数 \(\alpha_1,\cdots,\alpha_m\) 满足 \(\alpha_i\ge 0\),\(\sum\alpha_i=1\),\(Q=\sum\alpha_iP_i\),则根据加权算术-几何平均不等式,\(\sum\alpha_ix^{P_i.a}y^{P_i.b}=\sum\alpha_i(x^2)^{P_i.a/2}(y^2)^{P_i.b/2}\ge\prod((x^2)^{P_i.a/2}(y^2)^{P_i.b/2})^{\alpha_i}=(x^2)^{a/2}(y^2)^{b/2}=|x^ay^b|\),由此可得令凸包顶点的系数为 \(n\),内部点的系数为 \(1\) 时多项式有下界,得证。
再考虑 \(\Rightarrow\),设凸包顶点 \(P_k\) 的横纵坐标不全为偶数,不妨设横坐标为奇数。设凸包在 \(P_k\) 处的法向量为 \((p,q)\),则在所有 \(P_i\) 的 \(pa+qb\) 中 \(P_k\) 是唯一最值,若为最小值则将 \(p,q\) 取反,此时 \(p\cdot P_k.a+q\cdot P_k.b>p\cdot 0+q\cdot 0=0\),所以令 \(x=-t^p,y=t^q,t\rightarrow+\infty\) 时多项式的值 \(\rightarrow-\infty\),得证。
于是问题转化为给定点集问是否可以删去至多一个点,使得凸包上至少有一个点横纵坐标有至少一个是奇数。
官方题解给了个神奇做法:先求出凸包,然后给凸包顶点(除原点)交替染黑白色,分别对不是黑/白色的点求凸包,如果有一个有横纵坐标有奇数的顶点,那么 Ani 赢(可以),否则 Borna 赢。
至于证明,因为没给所以不会,感性理解就是若不删点就满足要求则显然正确,否则看得到的奇数顶点,至少且只需要删掉现有凸包顶点的一个区间,此时满足做法中判断的条件就说明只需要删去一个点,也是正确的。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200003;
template<typename T>
void rd(T &x){
int ch = getchar(); x = 0;
for(;ch < '0' || ch > '9';ch = getchar());
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
int n, m, tp, h[N], p[N];
struct Node {
int x, y, c;
Node(int _x = 0, int _y = 0, int _c = 0): x(_x), y(_y), c(_c){}
Node operator-(const Node &o) const {return Node(x-o.x, y-o.y);}
bool operator<(const Node &o) const {return x<o.x || x==o.x && y<o.y;}
LL operator*(const Node &o) const {return (LL)x*o.y-(LL)y*o.x;}
operator bool() const {return (x|y)&1;}
} a[N];
void work(int *h){
tp = 0;
for(int i = 0;i <= n;++ i) if(a[i].c != 1){
while(tp > 1 && (a[i]-a[h[tp]])*(a[h[tp]]-a[h[tp-1]])>=0) --tp;
h[++tp] = i;
} int tmp = tp;
for(int i = n-1;~i;-- i) if(a[i].c != 1){
while(tp > tmp && (a[i]-a[h[tp]])*(a[h[tp]]-a[h[tp-1]])>=0) --tp;
h[++tp] = i;
}
}
int main(){
rd(n);
for(int i = 1;i <= n;++ i){
rd(a[i].x); rd(a[i].y);
} sort(a, a+n+1);
work(p); m = tp;
for(int i = 2;i < tp;++ i) if(a[p[i]]){puts("Ani"); return 0;}
for(int i = 2;i < m;++ i) a[p[i]].c = 2 - (i&1);
work(h);
for(int i = 2;i < tp;++ i) if(a[h[i]]){puts("Ani"); return 0;}
for(int i = 2;i < m;++ i) a[p[i]].c = 1 + (i&1);
work(h);
for(int i = 2;i < tp;++ i) if(a[h[i]]){puts("Ani"); return 0;}
puts("Borna");
}