CF1388E Uncle Bogdan and Projections 题解
Link.
Description.
给定一些和 \(x\) 轴平行的线段,保证两两不交。
现在你需要找到一个向量,使得他们按照这个向量的方向都平移到 \(x\) 轴上,还是两两不交。
最小化平移后最左端点到最右端点的距离,输出这个最小值
Solution.
没想到凸性质,以为总共可行点是 \(O(n)\) 的。
首先,我们考虑枚举向量的角度,这个东西是无限的。
我们考虑无限转有限,因为必定是两个线段他们“擦过”时才可能取最小值。
所以我们直接暴力找出 \(O(n^2)\) 个有机会取到最小值得地方。
然后,我们发现,两个不在同一条直线上的线段,他们平移后,取得两个端点之间肯定是无解的。
所以我们考虑“扫描线”(CF 官方题解用词,但我觉得这根本算不上吧),把不可能的直接删掉。
就是排序后,如果当前这个点不管不插入或插入都被一个区间覆盖着,就显然不可行。
然后,我们得到了 \(O(n^2)\) 个可行的点,每判断一个最小值是 \(O(n)\) 的,总复杂度 \(O(n^3)\) 无法通过。
所以我们可以考虑有无什么特殊性质。
我们发现了,它具有凸性质!! 赶快上 wqs 二分
直接三分,我们发现复杂度很优良,达到了 \(O(n\log (n^2))=O(n\log n)\),可以通过此题。
所以这是碾标了?
Coding.
点击查看拉代码
//是啊,你就是那只鬼了,所以被你碰到以后,就轮到我变成鬼了{{{
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
template<typename T>inline void read(T &x)
{
x=0;char c=getchar(),bz=0;
for(;c<48||c>57;c=getchar()) if(!(c^45)) bz=1;
for(;c>=48&&c<=57;c=getchar()) x=(x<<1)+(x<<3)+(c^48);
bz?x=-x:x;
}/*}}}*/
int n,L[2005],R[2005],Y[2005],at,bt;double b[4000005];const double eps=1e-9;
struct node{double ps;int vl;char operator<(node b) const {return ps<b.ps;}}a[4000005];
inline double chk(double vl)
{
double mn=1e13,mx=-1e13;
for(int i=1;i<=n;i++) mn=min(mn,L[i]-vl*Y[i]),mx=max(mx,R[i]-vl*Y[i]);
return mx-mn;
}
int main()
{
read(n);for(int i=1;i<=n;i++) read(L[i]),read(R[i]),read(Y[i]);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(Y[i]<Y[j])
a[++at]=(node){1.0*(L[j]-R[i])/(Y[j]-Y[i])+eps,1},a[++at]=(node){1.0*(R[j]-L[i])/(Y[j]-Y[i])-eps,-1};
sort(a+1,a+at+1);if(!at) a[++at]=(node){0,0};
//for(int i=1;i<=at;i++) printf("%.10lf %d\n",a[i].ps,a[i].vl);
for(int i=1,v=0;i<=at;v+=a[i++].vl) if(!v||!(v+a[i].vl)) b[++bt]=a[i].ps;
//for(int i=1;i<=bt;i++) printf("%lf%c",b[i],i==bt?'\n':' ');
int l=1,r=bt;while(l+2<r) {int md1=(l+r)>>1,md2=md1+1;if(chk(b[md1])<chk(b[md2])) r=md2;else l=md1;}
double rs=1e13;for(int i=max(1,l-1);i<=min(r+2,bt);i++) rs=min(rs,chk(b[i]));
return printf("%.10lf\n",rs),0;
}