UVA1742 Oil

题意翻译

给出NN条互不相交的线段,保证所有线段都与xx轴平行,每一条线段的价值为它的长度。试求一条直线,满足:①不与xx轴平行;②与这条直线相交的所有线段的价值之和最大,求出这个最大的价值和。

输入格式:

有若干组输入数据。每组输入数据的第一行一个正整数NN表示线段的数量,接下来NN行每行三个整数x_1,x_2,yx1​,x2​,y表示一条线段的两个端点为(x_1,y)(x1​,y)与(x_2,y)(x2​,y)

输出格式:

对于每组输入数据,对应一行输出最大的价值总和。

数据范围:

1 \leq N \leq 2000 , -10^6 \leq x_1,x_2 \leq 10^6 , 1 \leq y \leq 10^61≤N≤2000,−106≤x1​,x2​≤106,1≤y≤106

这是一道很考验思维的黑题,而且还需注意许多细节,

题意即为平面内有 nn 条与 xx 轴不相交的的直线(zher~xian)且它们互不相交,

你要找出一条与 xx 轴相交的直线(即不与 //x//x 轴),

使得这条直线经过的所有线段的权值和最大,

线段の权值即为线段的长度,

这时我们可以想到枚举每个线段的左端点,

令当前线段为 ll,

令与直线 ll,yy 坐标不相等の其他线段为 mm,

即为 ll 的左端点到 mm 的左端点的直线的斜率,

和 ll 的左端点到 mm 的右端点的直线的斜率,

只要某个斜率 kk 在这个区间范围内,直线 ll,mm 都会被它穿过,

为什么强调 yy 坐标不等呢?因为线段之间互不重复,如果 yy 坐标相等,

想要同时穿过这两条线段且与 xx 轴相交是不可能的。所以排除。

这里存斜率时有一个好方法,

我们存斜率的倒数,因为当一条直线 //y//y 轴时不存在斜率,

但是这种直线是可以取的,

反之,//x//x 的直线时在本题不存在,且它的斜率为 00,

如果我们将斜率转换为原来的倒数这样原来不存在斜率的直线斜率为00,

反之原来斜率为 00 的直线不存在斜率,

这样一来,我们就处理好了斜率,

接下来就是考虑尺取法求那条直线是最优解,

对于每条线段 ii 我们都假定 ii 一定会被取到,

枚举其他线段且能同时能覆盖 ii 的斜率的区间并将它们按左端点大小递增排序,

我们再创建一个相同的结构体数组同样维护两个斜率以及自己线段的编号,

并将第二个数组按右端点大小递增排序,

再枚举 ll 数组里的所有线段,令它为 jj,先假定 jj 一定会被覆盖,

令第一个数组名为 ll,第二个为 rr,sumsum 为左斜率小于等于 jj 的当前覆盖线段的总和,

那么斜率区间不与 jj 重叠的部分我们要去掉,

什么时候不与 jj 重叠呢?

令当前 rr 区间最小右斜率值的线段记为 pp,

当 jj 的左斜率大于 pp 的右斜率时,jj & pp 不存在交集,所以保证取到 jj 时

pp 一定取不到,所以 sumsum 要减去 pp 的贡献

并且我们不用担心 pp 的左斜率大于 jj 的右斜率的情况,

因为如果 pp 的左斜率比 jj 的右斜率大时,

pp 的左斜率一定大于 jj 的左斜率(不符合 sumsum の定义),

因为 ll 数组的左斜率值保持递增,所以 pp 在后面一定会被枚举到,

直到 pp 的右斜率 >=>= jj 的左斜率时,停止弹出操作

弹出完后, sumsum 的值就为某条直线 kk 覆盖 ii , jj ( q_1q1​, q_2q2​,......)这些线段的权值总和,

注:( q_1q1​, q_2q2​...)的左斜率值均小于等于 jj,

再记录全局最大 sumsum 即可

再来个解答(如果 q_nqn​ 的左斜率值大于 jj 的左斜率值时也能被取到怎么处理?)

我们不妨反过来想那么枚举的 jj 等于 q_nqn​ 时曾经的 jj 不就会同它一起覆盖了吗!

Code(Accept 100pts):

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long double ld;
typedef long long ll;
const int N=2e3+10;
struct lim{
    ld lk,rk;
    int id;
}l[N],r[N];
struct line{
	int x1,x2,y;
}s[N];
int n,p,a,b,c,tot;
ll ans,sum;
bool cmpl(lim x,lim y){return x.lk<y.lk;}
bool cmpr(lim x,lim y){return x.rk<y.rk;}
inline ld k(int x1,int y1,int x2,int y2){
	return (ld)(x1-x2)/(y1-y2);
}
int main(){
    for(;~scanf("%d",&n);){
        ans=0;
        for(int i=1;i<=n;++i){
            scanf("%d%d%d",&a,&b,&c);
            if(a>b)swap(a,b);
            s[i]=(line){a,b,c};
        }
        for(int i=1;i<=n;++i){
            tot=0;
            for(int j=1;j<=n;++j){
            	 if(s[i].y!=s[j].y){
                    l[++tot].lk=k(s[i].x1,s[i].y,s[j].x1,s[j].y);
                    l[tot].rk=k(s[i].x1,s[i].y,s[j].x2,s[j].y);
                    l[tot].id=j;
                    if(l[tot].lk>l[tot].rk)swap(l[tot].lk,l[tot].rk);
                    r[tot]=l[tot];
                }
			}
            sort(l+1,l+tot+1,cmpl),sort(r+1,r+tot+1,cmpr);
            p=1;
            sum=s[i].x2-s[i].x1;
            ans=max(ans,sum);
            for(int j=1;j<=tot;++j){
                sum+=s[l[j].id].x2-s[l[j].id].x1;
                for(;r[p].rk<l[j].lk;++p)sum=sum-(s[r[p].id].x2-s[r[p].id].x1);
                ans=max(ans,sum);
            }
        }
        printf("%lld\n" ,ans);
    }
    return 0;
}
/*
5
100 180 20
30 60 30
70 110 40
10 40 50
0 80 70
3
50 60 10
-42 -42 20
25 0 10
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值