P3696,玄妙的贪心题

首先把中场的每个团体看做左括号。结束时的每个团体看做右括号。然后按照人气值从小到大排序(实际上因为初始输入就有序,可以用归并来代替排序)。

这样我们就得到了一个括号序列。每个括号都有一个颜色(即其学校编号)。然后要删掉尽量多的同色括号对,并使得剩下的括号序列仍然合法。

从左往右考虑每个括号,一开始花了一个下午想了五种O(n)贪心,一一拍炸,然而拍炸前总觉得是对的。这里就不赘述这些贪心了。

一个无所作为的下午告诉我,O(n)的贪心是不行的,这就启示我们用数据结构去优化。

将左括号视为1,右括号视为-1,那么一个合法的括号序列,其所有前缀和一定非负。

考虑删掉一对括号对前缀和的影响,这相当于对前缀和区间减。

然而要保证前缀和非负,所以减的区间中,最小值要大于零。

考虑一个右括号能否找到与之匹配,且一起删掉后合法的左括号。记ss为这个右括号以左第一个为零的前缀和所在的位置,那么左括号一定要在那个位置以右,并且二者同色。

用线段树实现区间加和区间最小值,查询ss时二分,就得到了优越拙劣的O(nlog2n)做法,实际上有O(nlogn)的题解,但实际效率好像还不错。

另外说一句,这道题里票数可以为零,这,坑了我很久,甚至对拍也拍不出。以后还是要注意,这个错误把我坑到45分(其中25分是常数太大)

#include<cstdio>
#include<cctype>
#include<vector>
const int N=400010,L=1000000;
char ibuf[L],*ih=ibuf+L;
inline char gc(){return ih==ibuf+L?fread(ibuf,1,L,stdin),ih=ibuf,*ih++:*ih++;}
inline void read(int&x){
    static char c;
    for(c=gc();!isdigit(c);c=gc());
    for(x=0;isdigit(c);c=gc())x=x*10+c-48;
}
std::vector<int> s[N];
int n,i,j,ans,ss,aa[N],bb[N],xb,ea[N],eb[N],l,r,m;
struct person{int x,y;}a[N],b[N];
inline int min(int a,int b){return a>b?b:a;}
struct segtree{
    struct node{
        int l,r,m,s,mn;
    }t[N<<2];
    void build(int i,int l,int r){
        t[i].l=l,t[i].r=r;t[i].m=(l+r)>>1;
        if(l==r)return;build(i<<1,l,t[i].m),build(i<<1|1,t[i].m+1,r);
    }
    inline void add(int i,int l,int r,int v){
        if(t[i].l==l && t[i].r==r)t[i].s+=v,t[i].mn+=v;
            else{
                if(t[i].m<l)add(i<<1|1,l,r,v);
                    else if(r<=t[i].m)add(i<<1,l,r,v);
                            else add(i<<1,l,t[i].m,v),add(i<<1|1,t[i].m+1,r,v);
                t[i].mn=(t[i<<1].mn>t[i<<1|1].mn?t[i<<1|1].mn:t[i<<1].mn)+t[i].s;
            }
    }
    inline int query(int i,int l,int r){
        if(t[i].l==l && t[i].r==r)return t[i].mn;
        return (r<=t[i].m?query(i<<1,l,r):(l>t[i].m?query(i<<1|1,l,r):min(query(i<<1,l,t[i].m),
            query(i<<1|1,t[i].m+1,r))))+t[i].s;
    }
}t;
int main(){
    read(n);ans=n;t.build(1,1,n<<1);
    for(i=1;i<=n;++i)read(a[i].x),read(a[i].y);
    for(i=1;i<=n;++i)read(b[i].x),read(b[i].y);s[b[1].x].push_back(1);aa[xb=1]=1;bb[1]=1;
    t.add(1,1,n<<1,1);eb[1]=1;b[n+1].y=-1;
    for(i=j=1;i<=n;++i){
        while(b[j+1].y>=a[i].y)++j,aa[eb[j]=++xb]=j,s[b[j].x].push_back(xb),bb[xb]=1,
            t.add(1,xb,n<<1,1);
        aa[ea[i]=++xb]=i,bb[xb]=-1;t.add(1,xb,n<<1,-1);
        for(l=ss,r=xb-1;l<r;){
            m=(l+r+1)>>1;
            if(t.query(1,m,xb-1))r=m-1;
                else l=m;
        }
        ss=l;
        if(!s[a[i].x].empty() && s[a[i].x].back()>ss)--ans,t.add(1,s[a[i].x].back(),ea[i]-1,-1),
            s[a[i].x].pop_back();
    }
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值