传送门
题意:在平面直角坐标系中给出$N$条互不相交的、与$x$轴平行、且在$x$轴上方的线段,每一条线段的价值为其长度。求一条不与$x$轴平行的直线,使得与这条直线相交的线段的价值之和最大,求出这个和。$N \leq 2000 , -10^6 \leq \text{线段两端点横坐标} \leq 10^6 , 1 \leq \text{线段两端点纵坐标} \leq 10^6$
首先我们可以想到:必定存在一条最优的直线,它经过某一条线段的左端点和某条线段的右端点,因为如果某条最优的直线不满足这些性质,我们可以通过旋转和平移使其满足条件。基于上面的结论,我们可以很轻松地想到一种算法:枚举直线经过哪一条线段的左端点和哪一条线段的右端点,然后扫一遍所有的线段看其是否被该直线穿过。这样子的复杂度是$O(n^3)$的。
接着我们考虑优化。我们这样考虑:枚举直线经过其左端点的线段,那么某一条线段会产生贡献当且仅当直线的斜率在某一个区间之内。那么我们将所有线段的会产生贡献的区间算出来,问题就转化为了:给定若干区间,每个区间有权值,选择一个点使得包含这个点的区间的权值之和最大。这个问题可以利用尺取法:按照左端点从小到大加入区间,删除与新加入的区间不相交的区间,计算剩余的权值和。这种算法的复杂度就降为了$O(n^2logn)$
一个细节:为了避免区间不连续、斜率不存在的情况,我们可以计算每一个线段产生贡献时,直线斜率的倒数会落在哪一段区间$($我们认为直线的斜率不存在的时候,斜率的倒数为$0)$,这样每一个线段的贡献区间就是连续的,还可以在计算斜率时避免除$0$。
1 //一份常数极大的Code 2 #include<bits/stdc++.h> 3 #define ld long double 4 #define eps 1e-12 5 using namespace std; 6 7 inline int read(){ 8 int a = 0; 9 bool f = 0; 10 char c = getchar(); 11 while(!isdigit(c)){ 12 if(c == '-') 13 f = 1; 14 c = getchar(); 15 } 16 while(isdigit(c)){ 17 a = (a << 3) + (a << 1) + (c ^ '0'); 18 c = getchar(); 19 } 20 return f ? -a : a; 21 } 22 23 struct range{ 24 ld l , r; 25 int ind; 26 }now[2010] , ano[2010]; 27 int num[2010][3]; 28 29 inline long long max(long long a , long long b){ 30 return a > b ? a : b; 31 } 32 33 inline void swap(int &a , int &b){ 34 int t = a; 35 a = b; 36 b = t; 37 } 38 39 inline void swap(ld &a , ld &b){ 40 ld t = a; 41 a = b; 42 b = t; 43 } 44 45 bool cmp1(range l , range r){ 46 return l.l < r.l; 47 } 48 49 bool cmp2(range l , range r){ 50 return l.r < r.r; 51 } 52 53 inline ld calcK(int x1 , int y1 , int x2 , int y2){ 54 return (ld)(x1 - x2) / (y1 - y2); 55 } 56 57 int main(){ 58 int N; 59 while(scanf("%d" , &N) != EOF){ 60 for(int i = 1 ; i <= N ; i++){ 61 num[i][0] = read(); 62 num[i][1] = read(); 63 num[i][2] = read(); 64 if(num[i][1] < num[i][0]) 65 swap(num[i][1] , num[i][0]); 66 } 67 long long maxN = 0; 68 for(int i = 1 ; i <= N ; i++){ 69 int cnt = 0; 70 for(int j = 1 ; j <= N ; j++) 71 if(num[i][2] != num[j][2]){ 72 now[++cnt].l = calcK(num[i][0] , num[i][2] , num[j][0] , num[j][2]); 73 now[cnt].r = calcK(num[i][0] , num[i][2] , num[j][1] , num[j][2]); 74 now[cnt].ind = j; 75 if(now[cnt].l > now[cnt].r) 76 swap(now[cnt].l , now[cnt].r); 77 now[cnt].l -= eps; 78 now[cnt].r += eps; 79 ano[cnt] = now[cnt]; 80 } 81 sort(now + 1 , now + cnt + 1 , cmp1); 82 sort(ano + 1 , ano + cnt + 1 , cmp2); 83 int p = 1; 84 long long ans = num[i][1] - num[i][0]; 85 maxN = max(maxN , ans); 86 for(int j = 1 ; j <= cnt ; j++){ 87 ans += num[now[j].ind][1] - num[now[j].ind][0]; 88 while(ano[p].r < now[j].l){ 89 ans -= num[ano[p].ind][1] - num[ano[p].ind][0]; 90 ++p; 91 } 92 maxN = max(maxN , ans); 93 } 94 } 95 printf("%lld\n" , maxN); 96 } 97 return 0; 98 }