拟阵的一次实际运用及思考

· · 题解

相关题目:

P4511 [CTSC2015]日程管理

写在前面:

本题只是一道贪心题,而贪心策略也并不是很难想到。但其正确性却不是特别显然,并且贪心策略也不是能一步到位的(可能是因为本人太菜了)。

笔者发现,借助拟阵去思考这个问题的时候,其贪心策略与其正确性变得顺理成章,大大简化了思考,且使问题变得十分优雅,故写一篇博客总结。(尽管这不是本题所必要的)

当然啦本文不一定严谨,有问题欢迎指出

题意:

solution:

首先介绍一个小Trick:判断当前任务集合是否合法可以借助线段树:

先把第i个位置初值赋为i,然后对于一个t_i,将[t_i,n]区间减1,最后查询全局最小值是否非负即可,正确性显然(参考CF338E)

此题有一些明显的贪心策略,例如不带修改时可以按p_i从大到小把任务加入等。但正如前言所说,这些做法的正确性并不是非常显然(尽管感觉上是对的)。接下来我们换个角度看问题

U表示全部任务构成的集合,\mathscr F表示所有可以完成的任务集合构成的集族,我们尝试说明(U,\mathscr F)是一个拟阵

遗传性质显然,考虑证明其交换性质:

A,B\in \mathscr F,且|A|<|B|,欲证\exists x \in B,x\notin A,使A \bigcup \{ x \} \in \mathscr F

由于将某个任务完成时间提前后显然更有可能合法,考虑先安排AB中的任务的完成时间使其尽可能提早完成,即做到不存在任何一个任务能更早地完成

大概长这样:

考虑A的第一个没有任务占用的时间t(比如图中的第五个位置),对于B在该位置之后的所有任务的集合S,分情况:

可以发现第二种情况经过若干次递归后总能回到情况一

至此,交换性质得证,(U,\mathscr F)是一个拟阵

接下来的做法就很容易想了。

首先是不带修改操作时的暴力,这相当于是求拟阵带权最大独立集,说的通俗点,就是kruskal——这问题已经变得跟最小生成树没什么区别了

进一步考虑只有加入操作时怎么做。从拟阵的角度,这个问题等价于动态加边最小生成树:

先强制加入当前的边,再判断有没有成环。如果成环,就删去环上最小边。

考虑本题中环是什么,其实就是所有删去后能使任务集合合法的所有任务。

还记得开始时的那个线段树吗?我们先找到最左边的<0的位置pos,然后所有满足t_i \in [1,pos]的任务就是环上的任务了,删去其中最小的即可

至于删除,由于拟阵中的基大小相同,删掉一个任务后我们至多只能再加入一个任务,那就找能加入的任务中最大的。

我们可以找到当前线段树上最右的0的位置pos',然后找满足t_i \in (pos',n]的元素。由于删掉了一个任务后拟阵的秩可能减小,所以可能会找不到这样的元素。找不到就不加入了呗)

至于怎么实现,可以用线段树套set实现查找t_i在某一区间内的p_i最大或最小(或者树套树也行?)反正这就是具体实现的问题了,不在本文范围内

另外吐槽一句,开始时我没去想删除怎么做,因为删除可以以1个log的代价转为撤销,外面套个时间线段树就行了。不过感觉会被卡掉过不去。后来又想了一下发现删除好像也不难啊。。。

总结:

从本文中可以发现,拟阵并没有给出什么独特的思路(那些贪心策略即使不知道拟阵也能想到),但却使本题系统化,并且使思考贪心策略时不用担心其正确性,对于简化思路方面起到了很大的作用,也使问题变得更加优美。可见拟阵确实为一种好的工具。

AC代码:(人傻常数大,开O2才能过,开O2能快5倍我也不知道为啥)

#include <bits/stdc++.h>
#define LL int
using namespace std;

const LL maxn=1200000+10;
const LL inf=1e9;

LL n,Q;

struct legal{
    LL sp[maxn],add[maxn];
    inline void up(LL id){sp[id]=min(sp[id*2],sp[id*2+1]);}
    void build(LL id,LL l,LL r){
        add[id]=0;
        if (l==r) {sp[id]=l; return;}
        LL mid=(l+r)/2;
        build(id*2,l,mid); build(id*2+1,mid+1,r);
        up(id);
    }
    void init(){ build(1,1,n);}
    inline void down(LL id){
        if (add[id]){
            sp[id*2]+=add[id]; sp[id*2+1]+=add[id];
            add[id*2]+=add[id]; add[id*2+1]+=add[id];
            add[id]=0; 
        }
    }
    void updata(LL id,LL l,LL r,LL x,LL y,LL v){
        if (x<=l && r<=y){
            sp[id]+=v; add[id]+=v;
            return;
        }
        down(id);
        LL mid=(l+r)/2;
        if (x<=mid) updata(id*2,l,mid,x,y,v);
        if (y>mid) updata(id*2+1,mid+1,r,x,y,v);
        up(id);
    }
    inline void ins(LL t){updata(1,1,n,t,n,-1);}
    inline void del(LL t){updata(1,1,n,t,n,1);}
    bool ok(){return sp[1]>=0;}
    bool full(){return sp[1]<=0;}
    LL query_l(LL id,LL l,LL r,LL mi){
        if (l==r) return l;
        down(id);
        LL mid=(l+r)/2;
        if (sp[id*2]==mi) return query_l(id*2,l,mid,mi);
        else return query_l(id*2+1,mid+1,r,mi);
    }
    LL query_r(LL id,LL l,LL r,LL mi){
        if (l==r) return l;
        down(id);
        LL mid=(l+r)/2;
        if (sp[id*2+1]==mi) return query_r(id*2+1,mid+1,r,mi);
        else return query_r(id*2,l,mid,mi);
    }
    inline LL lpos(){
        if (sp[1]>=0) return n;
        return query_l(1,1,n,sp[1]);
    }
    inline LL rpos(){
        if (sp[1]>0) return 0;
        return query_r(1,1,n,sp[1]);
    }
}L;

struct Pair{
    LL first,second;
    bool operator<(const Pair&p) const{
        return first==p.first?second<p.second:first<p.first;
    }
    bool operator>(const Pair&p) const{
        return first==p.first?second>p.second:first>p.first;
    }
    bool operator==(const Pair&p) const{
        return first==p.first && second==p.second;
    }
};

struct dque{
    priority_queue<Pair > q,dq;
    inline void push(const Pair&x){
        q.push(x);
    }
    inline void del(const Pair&x){
        dq.push(x);
    }
    inline Pair top(){
        while (!q.empty() && !dq.empty() && q.top()==dq.top()){
            q.pop(); dq.pop();
        }
        if (q.empty()) return (Pair){-inf,0};
        return q.top();
    }
    inline bool empty(){
        while (!q.empty() && !dq.empty() && q.top()==dq.top()){
            q.pop(); dq.pop();
        }
        return q.empty();
    }
};

inline void max(Pair &x,const Pair &y){
    if (y>x) x=y;
}

struct segmentree{
    LL sp[maxn];
    long long sum;
    unordered_map<unsigned long long,LL> mp;
    dque val[maxn];
    segmentree(){sum=0;}
    inline void up(LL id){
        sp[id]=val[sp[id*2]].top()>val[sp[id*2+1]].top()?sp[id*2]:sp[id*2+1];
    }
    void build(LL id,LL l,LL r){
        if (l==r) {sp[id]=l; return;}
        LL mid=(l+r)/2;
        build(id*2,l,mid); build(id*2+1,mid+1,r);
        up(id);
    }
    void updata(LL id,LL l,LL r,LL t,LL p){
        if (l==r){
            val[t].push((Pair){p,t});
            return;
        }
        LL mid=(l+r)/2;
        if (t<=mid) updata(id*2,l,mid,t,p);
        else updata(id*2+1,mid+1,r,t,p);
        up(id);
    }
    void del(LL id,LL l,LL r,LL t,LL p){
        if (l==r){
            val[t].del((Pair){p,t});
            return;
        }
        LL mid=(l+r)/2;
        if (t<=mid) del(id*2,l,mid,t,p);
        else del(id*2+1,mid+1,r,t,p);
        up(id);
    }
    inline void ins(LL t,LL p) {updata(1,1,n,t,p); mp[12345678ull*p+t]++; sum+=p;}
    inline void del(LL t,LL p){del(1,1,n,t,p);mp[12345678ull*p+t]--; sum-=p;}
    inline bool exist(LL t,LL p){return mp[12345678ull*p+t];}
    Pair query(LL id,LL l,LL r,LL x,LL y){
        if (val[sp[id]].empty()) return (Pair){-inf,0};
        if (x<=l && r<=y) return val[sp[id]].top();
        LL mid=(l+r)/2; Pair ans=(Pair){-inf,0};
        if (x<=mid) max(ans,query(id*2,l,mid,x,y));
        if (y>mid) max(ans,query(id*2+1,mid+1,r,x,y));
        return ans;
    }
    inline Pair qry(LL x,LL y){return query(1,1,n,x,y);}
}T1,T2;

void add(LL t,LL p){
    L.ins(t); T1.ins(t,-p);
    if (L.ok()) return;
    LL pos=L.lpos();
    Pair P=T1.qry(1,pos);
    LL pp=-P.first,tt=P.second;
    if (pp<-1e8) return;
    T1.del(tt,-pp); T2.ins(tt,pp);
    L.del(tt);
}

void del(LL t,LL p){
    if (T2.exist(t,p)) {
        T2.del(t,p); return;
    }
    T1.del(t,-p); L.del(t);
    LL pos=L.rpos();
    Pair P=T2.qry(pos+1,n);
    LL pp=P.first,tt=P.second;
    if (pp<-1e8) return;
    T2.del(tt,pp); T1.ins(tt,-pp);
    L.ins(tt);
}
inline LL read(){
    LL x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline string reads(){
    string res; char ch=getchar();
    while (ch!=' ') res=res+ch,ch=getchar();
    return res;
}

int main(){
    n=read(),Q=read(); L.init(); 
    T1.build(1,1,n); T2.build(1,1,n);
    for (LL i=1;i<=Q;i++){
        string op;LL x,y;
        op=reads();
        x=read(),y=read();
        if (op[0]=='A') add(x,y);
        else del(x,y);
        printf("%lld\n",-T1.sum);
    }
    return 0;
}