JZOJ 3639 COCI2013 odasiljaci

Description

这里写图片描述

Input

这里写图片描述

Output

这里写图片描述

Sample Input

输入1:

3 10

1 2 6

0 4 3

0 8 2

输入2:

5 15

0 4 3

1 5 5

1 6 6

0 9 2

0 10 3

Sample Output

输出1:

6.000000

输出2:

8.500000

Data Constraint

30%的数据,N<=1000

Hint

这里写图片描述

分析

对于一个区间x,y,我们对于除了x,y以外的发射器忽略,考虑x-y之间的。
假设有若干个发射器,每个能覆盖区间z-y,那么我们可以找到一个最小的z,记为z1。
同理,假设这若干个发射器能覆盖区间x-z,同样可以找到一个最大的z,记为z2.
那么x,y这个区间对于答案的贡献就是y-x-max(0,z1-z2).
现在问题就是如何求z1,z2。很明显我们可以发现,其实z2就是z1倒过来求一波就可以了,所以我们只讨论如何求解z1.
我们首先从左到右扫一遍,用个数据结构(stl速度起飞)维护一下出现过的发射器,接下来我们具体讨论怎么维护。
首先定义一种东西叫做配对点,意思就是,对于每个发射器,他不能覆盖范围的最左(比如样例中第一个发射器的配对点就是2.5)
注意在扫之前先按照x升序排序。
在扫的时候维护一个三元组:x,h,t分别表示发射器的横坐标,高度和配对点。
接下来我们只讨论两个发射器A,B之间的情况(保证A的横坐标小于B的)
1.假如H(A)<=H(B),弹出B,因为A比B优。
2.否则,如果T(A)<
T(B),同样也能弹出B,理由如上。
然后注意维护的时候,要保证数据结构中的发射点,高度按照降序,配对点反而是升序(这个其实是个性质,不用单独维护)。
一开始如果建筑不是发射点,就把前两个发射点先加入数据结构中。。
在从左到右的过程中,一边走一边维护最新加入点的配对点(前面加入过的不用维护,因为不会改变),如果有T(A)>T(B),那么踢出B,(A为次新点,B为最新点),一直踢出,直到不能符合上式。。
这样数据结构中最后剩下的点就是能够做出贡献的点辣!
同样的方法,反着做一遍,求出z2,最后直接求解就好了。
时间复杂度经过证明(证明懒得写了,自己看英文题解吧)是O(n)。

代码

#include <bits/stdc++.h>

#define N 500005

int n,l;
int t[N],x[N],h[N];

struct NOTE
{

    NOTE(int a):x(a),y(0){}
    NOTE(int a,int b):x(a),y(b){}

    int x,y;
};

double ansl[N],ansr[N];

double getPos(const NOTE & now)
{
    int &x1 = x[now.x];
    int &y1 = h[now.x];
    int &x2 = x[now.y];
    int &y2 = h[now.y];
    return ((double)x1 * (y1 - y2) - (double)y1 * (x1 - x2)) / (y1 - y2);
}

void add(NOTE &now,int c)
{
    if (getPos(now) < getPos(NOTE(now.x,c)))
        now.y = c;
}

void getAnsPos(double *ans)
{
    std::vector<NOTE> V;
    for (int i = 0; i < n; i++)
    {
        for (;V.size() && h[V.back().x] <= h[i];)
        {
            V.pop_back();
        }
        if (V.size())
        {
            for (add(V.back(),i); V.size() > 1; V.pop_back())
            {
                add(V[V.size() - 2],i);
                if (getPos(V.back()) < getPos(V[V.size() - 2]))
                    break;
            }
        }
        if (t[i])
        {
            V.push_back(i);
            ans[i] = x[i];
        }
        else
            if (V.size())
                ans[i] = std::min(getPos(V.back()),(double)x[i + 1]);
            else ans[i] = x[i + 1];
    }
}

int main()
{
    freopen("odasiljaci.in","r",stdin);
    freopen("odasiljaci.out","w",stdout);
    scanf("%d%d",&n,&l);
    n++;
    for (int i = 1; i < n; i++)
        scanf("%d%d%d",&t[i],&x[i],&h[i]);
    x[n] = l;
    n++;

    getAnsPos(ansr);

    std::reverse(t,t + n);
    std::reverse(x,x + n);
    std::reverse(h,h + n);

    for (int i = 0; i < n; i++)
        x[i] = l - x[i];

    getAnsPos(ansl);

    std::reverse(ansl,ansl + n);

    for (int i = 0; i < n; i++)
        ansl[i] = l - ansl[i];

    double ans = 0;
    for (int i = 0; i < n - 1; i++)
        if (ansr[i] > ansl[i + 1])
            ans += ansr[i] - ansl[i + 1];
    printf("%.6lf\n",l - ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值