题目
思路
毫无疑问,到达塔台的时间是 − x v \frac{-x}{v} v−x (可以从向量角度思考)。
所以,如果进行风速为 v v v 的微调之后,二者能够同时到达,即 − x i v i + v = − x j v j + v \frac{-x_i}{v_i+v}=\frac{-x_j}{v_j+v} vi+v−xi=vj+v−xj
可以解出(交叉相乘就好) ( x i − x j ) v = v i x j − x i v j (x_i-x_j)v=v_ix_j-x_iv_j (xi−xj)v=vixj−xivj
稍微处理一下,把每一项都除以 x i x j x_i x_j xixj ,得到 ( 1 x j − 1 x i ) v = v i x i − v j x j \left(\frac{1}{x_j}-\frac{1}{x_i}\right)v=\frac{v_i}{x_i}-\frac{v_j}{x_j} (xj1−xi1)v=xivi−xjvj
很明显,这个式子的意义是 v v v 为 ( − 1 x i , v i x i ) , ( − 1 x j , v j x j ) (-\frac{1}{x_i},\frac{v_i}{x_i}),(-\frac{1}{x_j},\frac{v_j}{x_j}) (−xi1,xivi),(−xj1,xjvj) 两个点的斜率。
这两个点的形式是一样的,就会让题目变的简单起来:有 n n n 个点,求有多少点对,满足斜率的绝对值不超过 w w w 。
把这些点按照 x x x 坐标排个序,然后用所有的点减去不可行的点。更准确地说,
存储一个红色的线(斜率 − w -w −w ),一个橙色的线(斜率 + w +w +w ),这种不可行的点就刚好满足其中一个条件:
- 红色的线高于当前点的红色线。
- 橙色的线低于当前点的橙色线。
这可以用平衡树进行维护。或者,你直接将其离散化,树状数组求取。
p . s . p.s. p.s. 之所以满足 ∣ w ∣ < min i = 1 n ∣ v i ∣ |w|<\min_{i=1}^{n}|v_i| ∣w∣<mini=1n∣vi∣ ,是因为这样一来,无需验证是否有因为风而无法到达的飞机。
代码
卡精度,需要手写分数类。
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0' or c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c and c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(long long x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x%10)^48);
}
# define MB template < class T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }
struct Fraction{
typedef long long int_;
int_ son, mom; // 分“子”,分“母”
Fraction(const int_ &x=0,const int_ &y=1){
int_ d = __gcd(x,y);
son = x/d, mom = y/d; // 值为x/y
if(mom < 0) son = -son, mom = -mom;
}
bool operator < (const Fraction &that) const {
return son*that.mom < mom*that.son;
}
Fraction operator * (const int &num) const {
return Fraction(son*num,mom);
}
Fraction operator + (const Fraction &that) const {
return Fraction(son*that.mom+mom*that.son,mom*that.mom);
}
Fraction operator - () const {
return Fraction(-son,mom);
}
bool operator == (const Fraction &that) const {
return son == that.son and mom == that.mom;
}
};
struct Point{
Fraction x, y;
bool operator < (const Point &that) const {
return x < that.x;
}
};
const int MaxN = 100005;
Point p[MaxN]; int n, w;
Fraction l[MaxN], r[MaxN];
int c1[MaxN], c2[MaxN];
int main(){
n = readint(), w = readint();
for(int i=1,x,v; i<=n; ++i){
x = readint(), v = readint();
p[i].x = Fraction(1,x);
p[i].y = Fraction(-v,x);
}
for(int i=1; i<=n; ++i)
l[i] = (-p[i].x)*w+p[i].y;
for(int i=1; i<=n; ++i)
r[i] = (-p[i].x)*(-w)+p[i].y;
sort(l+1,l+n+1); int L = unique(l+1,l+n+1)-l;
sort(r+1,r+n+1); int R = unique(r+1,r+n+1)-r;
sort(p+1,p+n+1); long long ans = 0;
for(int i=1; i<=n; ++i){
int tmp = 0; // 合法的点
Fraction now = (-p[i].x)*w+p[i].y;
int id = lower_bound(l+1,l+L,now)-l;
for(int j=id-1; j; j-=(j&-j)) tmp -= c1[j];
for(int j=id; j<=n; j+=(j&-j)) ++ c1[j];
now = (-p[i].x)*(-w)+p[i].y;
id = lower_bound(r+1,r+R,now)-r;
for(int j=id; j; j-=(j&-j)) tmp += c2[j];
for(int j=id; j<=n; j+=(j&-j)) ++ c2[j];
ans += tmp;
}
printf("%lld\n",ans);
return 0;
}
后记
另一个很好的想法,来自于@C202044zxy 的博客:
如果风速最大时,我比它先到;风速最小时,我比它晚到,那么中间存在一个时刻,我俩同时到达。