bzoj 1178: [Apio2009]CONVENTION会议中心 (set+倍增)

题目描述

传送门

题解

这道题如果只有第一问的话,其实就是一个经典的线段覆盖问题。
我们对于所有线段按照右端点排序,如果当前线段的左端点大于上一条入选线段的右端点,那么答案+1.
这样就可以得到第一问的答案。
关键是第二问。注意是选中的编号升序后的字典序最小。
对于每个线段i,维护以i为开头的选择 2j 条线段( 2j 中不包括i,且选中的线段可以合法的存在)到达的线段编号,记作 f(i,j)
对于一条线段我们考虑如何判断是否可以插入呢?
设插入的线段为 [l0,r0] getans(l,r) 得到的是 [l,r] 最大的线段插入数量.
如果 getans(l,r)=getans(l,l01)+getans(r0+1,r)+1 ,那么 [l0,r0] 一定可以插入。
其中l表示的是小于l0的已经插入的线段的最大右端点,r表示的是大于r0的已经插入的线段的最小左端点。
光判断这个还不行,我们还需要判断[l0,r0]与已经入选的线段是否冲突,即是否会相交或者包含。
对于相交或者被包含,可以判断已经插入的所有左右端点中小于等于l0的最大的是不是区间的右端点,如果不是则说明相交,r0的判断也是同理。
对于包含,判断一下大于l0的第一个点是否是左端点,如果是左端点且小于r0,那么包含。
因为要字典序最小,所以我们从小到大枚举是否能插入即可。

代码

#include
#include
#include
#include
#include
#include
#include
#define N 200003
#define inf 1000000000
using namespace std;
struct data{
    int l,r,id;
}a[N],b[N],c[N];
int n,f[N][20],mark[N],cnt,ins[N];
set<int>s;
map<int,int> L,R;
int cmp(data a,data b){
    return a.rint cmp1(data a,data b){
    return a.idint cmp2(data a,data b){
    return a.lint getans(int x,int y)
{
    int l=1; int r=cnt+1; int pos=cnt+1;
    while (l<=r) {
        int mid=(l+r)/2;
        if (a[mid].r>=x&&a[mid].l>=x) pos=min(pos,mid),r=mid-1;
        else l=mid+1; 
    }
    if (pos==cnt+1) return 0;
    if (a[pos].r>y) return 0;
    x=a[pos].id;
    int ans=0;
    for (int i=17;i>=0;i--) {
        int t=f[x][i];
        if (t==n+1) continue;
        if (c[t].r<=y) ans+=(1<return ans+1;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) {
        scanf("%d%d",&b[i].l,&b[i].r);
        b[i].id=i;
    }
    for (int i=1;i<=n;i++) c[i]=b[i];
    sort(b+1,b+n+1,cmp);
    a[1]=b[1]; cnt=1; ins[b[1].id]=1;
    for (int i=2;i<=n;i++)
     if (b[i].l>a[cnt].l) {
        a[++cnt]=b[i];
        ins[b[i].id]=1;
    }
    int ans=1;int end=a[1].r;
    for (int i=2;i<=cnt;i++)
     if (a[i].l>end) {
        ans++;
        end=a[i].r;
     }
    printf("%d\n",ans); 
    for (int i=0;i<=17;i++) f[n+1][i]=n+1;
    for (int i=1;i<=cnt;i++) {
        int l=i+1; int r=cnt+1; int pos=n+1;
        while (l<=r) {
            int mid=(l+r)/2;
            if (a[mid].l>a[i].r) pos=min(pos,mid),r=mid-1;
            else l=mid+1;
        }
        if (pos!=n+1) f[a[i].id][0]=a[pos].id;
        else f[a[i].id][0]=n+1;
    }
    for (int i=1;i<=17;i++)
     for (int j=1;j<=cnt;j++) 
      f[a[j].id][i]=f[f[a[j].id][i-1]][i-1];
    memset(mark,0,sizeof(mark));
    s.insert(-inf); s.insert(inf);
    for (int i=1;i<=n;i++) {
        int l=*--s.lower_bound(c[i].l);
        int r=*s.lower_bound(c[i].r);
        int ll=*s.lower_bound(c[i].l);
        if (ll<=c[i].r) continue;
        if (L[c[i].l]||L[c[i].r]||R[c[i].l]||R[c[i].r]) continue;
        if (r!=inf&&R[r]&&!L[r]) continue;
        if (l!=-inf&&L[l]&&!R[l]) continue;
        if (l==-inf||R[l]) {
            int t=getans(l,c[i].l-1)+getans(c[i].r+1,r)+1;
            if (t!=getans(l,r)) continue;
            s.insert(c[i].l); s.insert(c[i].r); 
            R[c[i].r]=1; L[c[i].l]=1;
            if (ans>1) printf("%d ",i);
            else printf("%d\n",i);
            ans--;
            if (!ans) break;
        }
    }
}

你可能感兴趣的:(倍增,贪心,STL)