【BZOJ1178】会议中心(APIO2009)-贪心+倍增+set

测试地址:会议中心
做法:本题需要用到贪心+倍增+set。
如果这题单纯只求最多的线段数,相信大家都会贪心求了,但是鉴于我太弱今天才会,这里还是写一下吧。
把所有线段按左端点从小到大排序,按右端点为第二关键字从大到小排序,我们首先维护一个栈,是为了把所有包含其它线段的线段删掉,因为选这些线段一定不优。删完之后,这些线段的左右端点都是单调的了。那么我们贪心从小到大选择,一旦能选某条线段就马上选,因为单调性的缘故,这个算法一定是对的。
那么这题要求字典序最小的解,怎么办呢?按照套路,我们按字典序从小到大试验线段,如果选了这条线段后还能求出最优解,就选这条线段,这样选出来的解一定是最优且字典序最小的。
问题来了,我们要如何判断选了某些线段之后还能不能求出最优解呢?注意到我们要实现一个函数 calc(l,r) c a l c ( l , r ) ,表示所有线段的端点都在区间 [l,r] [ l , r ] 之间的最优解,那么我们每次往解中添加一条线段,令这条线段为 [lx,rx] [ l x , r x ] ,而它在我们已经选出的线段中的前驱右端点为 R R ,后继左端点为L,于是如果 calc(R+1,L1)=calc(R+1,lx1)+calc(rx+1,L1)+1 c a l c ( R + 1 , L − 1 ) = c a l c ( R + 1 , l x − 1 ) + c a l c ( r x + 1 , L − 1 ) + 1 ,就表示可以选这条线段。前驱和后继可以用set维护,那么现在的问题就是如何实现这个函数。
注意到解如果要最优,那么选的线段一定不能是能包含其它线段的线段,所以其实就是在我们一开始所述的贪心算法中的那些线段里选。而注意到我们贪心的过程,我们发现选了一条线段后,它之后紧接着选的线段是一定的,于是我们就可以用倍增来加速这个贪心的过程,即令 f(i,j) f ( i , j ) 为:如果一开始选了线段 i i ,那么贪心过程中取到的第2j条线段是哪条。那么显然有:
f(i,j)=f(f(f(i,j1),1),j1) f ( i , j ) = f ( f ( f ( i , j − 1 ) , 1 ) , j − 1 )
于是上面的 calc c a l c 函数就很好实现了,其实就是从第一条左端点大于等于 l l 的线段开始取,取的线段右端点不能超过r的贪心过程,用倍增加速这一过程即可。
那么我们就做完了这一题,时间复杂度为 O(nlogn) O ( n log ⁡ n )
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000100;
int n,st[200010],top,pos[200010],ans[200010];
int f[200010][25]={0},maxf;
struct interv
{
    int l,r,id;
    bool operator < (interv a) const
    {
        return l<a.l;
    }
}q[200010];
set<interv> S;
set<interv>::iterator it;

bool cmp(interv a,interv b)
{
    if (a.l!=b.l) return a.l<b.l;
    else return a.r>b.r;
}

void init()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id=i;
    }

    sort(q+1,q+n+1,cmp);
    top=0;
    for(int i=1;i<=n;i++)
        pos[q[i].id]=i;
    for(int i=1;i<=n;i++)
    {
        while(top&&q[st[top]].r>q[i].r) top--;
        st[++top]=i;
    }

    int now=1,nowr=0;
    maxf=0;
    for(int i=1;i<=top;i++)
    {
        f[i][0]=i;
        while(q[st[now]].r<q[st[i]].l) f[now][1]=i,now++;
        if (q[st[i]].l>nowr)
        {
            maxf++;
            nowr=q[st[i]].r;
        }
    }
    for(int i=2;i<=20;i++)
        for(int j=1;j<=top;j++)
            f[j][i]=f[f[f[j][i-1]][1]][i-1];
}

int calc(int x,int y)
{
    if (x>y) return 0;
    int l=1,r=top;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if (q[st[mid]].l<x) l=mid+1;
        else r=mid;
    }
    if (q[st[l]].l<x) return 0;

    int tot=0;
    for(int i=20;i>=0;i--)
        if (f[l][i]&&q[st[f[l][i]]].r<=y)
        {
            tot+=(1<<i);
            l=f[f[l][i]][1];
        }
    return tot;
}

void work()
{
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        int l=q[pos[i]].l,r=q[pos[i]].r,tot=0,L,R;

        it=S.lower_bound(q[pos[i]]);
        if (it!=S.end()) L=(*it).l;
        else L=inf;
        if (L<=r) continue;
        tot+=calc(r+1,L-1);

        if (it!=S.begin())
        {
            it--;
            R=(*it).r;
        }
        else R=0;
        if (R>=l) continue;
        tot+=calc(R+1,l-1);

        if (tot+1==calc(R+1,L-1))
        {
            ans[++cnt]=i;
            S.insert(q[pos[i]]);
        }
    }
    printf("%d\n",cnt);
    for(int i=1;i<=cnt;i++)
        printf("%d ",ans[i]);
}

int main()
{
    init();
    work();

    return 0; 
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值