[USACO08JAN]Haybale Guessing G

P2898

并查集做法确实很巧妙,但实际上最优解是线段树
目前本人luogu最优解第三,此处讲一下线段树的做法

首先二分不再赘述

矛盾的条件

  1. 同一数值,多次出现,几个区间间交集为空集(因为每个数值存在一个)
  2. 一个数值出现时,它的区间已经被较大值的区间覆盖(此时本数值放在区间内任意位置,都会使之前给出的信息不成立)

具体的操作:

  1. 预处理 对数值离散化
  2. 先把重复数值处理,求交集和并集(它的交集是它可以存在的区间,它的并集是它可以覆盖的区间),若无交集则矛盾
  3. 对二分取定的前 K 条信息进行排序,先处理较大数值(因为每个数值会不会被覆盖只跟大于它的数有关)
  4. 访问到某条信息时,先在线段树上查询本区间是否有位置未被覆盖, 若有则该数值还有生存空间,若无返回 0 ,然后修改它的区间

存储

众所周知这是个区间覆盖的问题,但是一个较小值的区间被哪个值覆盖并不重要, 所以线段树里只需要存 bool , 被覆盖则为 真 ,否则为 假

然后,因为要查询的是是否有未被覆盖的位置, 所以存的是区间所有数值的与, 也就是

\[t[k] = t[lt]\,\&\,t[rt] \]

修改&查询

  1. 用交集查询,用并集修改
  2. 修改时的操作全部都是 \(t[k]\,\|\,=1\)
    因为递归时当前树上节点对应区间越来越小,所以若当前节点值为 真 ,再往下也都是 真 ,所以就不用再往下递归
  3. 查询时同上条,若当前节点是 真 ,那么不再递归
  4. 当前节点值为 真 ,说明它子树节点都会是 真 ,仔细想想,这与 延迟标记 不是一样吗?所以节点值可以兼职 延迟标记

记得建树初始化(不用 pushup )

丑陋的代码:

#include<cstdio>
#include<cstring>
#include<cctype>
#include<vector>
#include<algorithm>
#define reg register int
#define ttT template <typename T>
#define EL puts("Elaina")
using namespace std;
inline char gc(){
    static char buf[1<<20],*p1,*p2;
    if(p1==p2){p1=buf,p2=buf+fread(buf,1,1<<20,stdin);if(p1==p2)return EOF;}
    return *p1++;
}
ttT inline void read(T &x){
    x=0;int f=1;char ch=gc();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
    x*=f;
}
ttT inline void write(T x){
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar((x%10)^48);
}
const int maxn=1e6+3,maxm=25000+3,INF=0x7fffffff;
int n,m,b[maxm];
struct qestion{
    int l,r,v;
}a[maxm];
struct nump{
    int l,r;//交
    int l2,r2;//并
}c[maxm];//下标为数值(草堆数)
#define lt (k<<1)
#define rt (k<<1|1)
struct tree{
    int l,r;
    bool v;
}t[maxn<<2];
inline void pushup(int k){
    t[k].v=t[lt].v&t[rt].v;
}
inline void pushdown(int k){
    if(t[k].v)t[lt].v=t[rt].v=true;
}
inline void build(int k,int l,int r){
    t[k].l=l,t[k].r=r,t[k].v=false;
    if(l==r)return;
    else{
        int mid=(l+r)>>1;
        build(lt,l,mid);
        build(rt,mid+1,r);
    }
}
inline bool query(int L,int R,int k){
    if(t[k].v)return true;
    if(L<=t[k].l&&t[k].r<=R)return t[k].v;
    else{
        pushdown(k);
        bool ans=true;
        if(L<=t[lt].r)ans&=query(L,R,lt);
        if(R>=t[rt].l)ans&=query(L,R,rt);
        return ans;
    }
}
inline void updata(int L,int R,int k){
    if(t[k].v)return;
    if(L<=t[k].l&&t[k].r<=R)t[k].v=true;
    else{
        pushdown(k);
        if(L<=t[lt].r)updata(L,R,lt);
        if(R>=t[rt].l)updata(L,R,rt);
        pushup(k);
    }
}
/*
1、相同数字,多次出现,某两个区间不重叠
2、修改后失去某个数值
对数值 离散化
修改:较大值覆盖较小值
*/
inline bool check(int k){
    if(!k)return true;
    memset(c,0,sizeof(c));
    vector <int> apr;//出现过的数值
    int minv=INF,maxv=-INF;
    for(reg i=1;i<=k;++i){
        int v=a[i].v;
        minv=min(minv,a[i].l),maxv=max(maxv,a[i].r);//需要建树的区间
        if(!c[v].l){
            c[v].l=c[v].l2=a[i].l,c[v].r=c[v].r2=a[i].r;
            apr.push_back(v);
        }
        else{
            c[v].l=max(c[v].l,a[i].l),c[v].r=min(c[v].r,a[i].r);
            if(c[v].l>c[v].r)return false;
            c[v].l2=min(c[v].l2,a[i].l),c[v].r2=max(c[v].r2,a[i].r);
        }
    }
    sort(apr.begin(),apr.end());
    build(1,minv,maxv);
    for(reg i=apr.size()-1;i>=0;--i){
        int v=apr[i];
        if(query(c[v].l,c[v].r,1))return false;
        updata(c[v].l2,c[v].r2,1);
    }
    return true;
}
void solve(){
    read(n),read(m);
    for(reg i=1;i<=m;++i){
        read(a[i].l),read(a[i].r),read(a[i].v);
        b[i]=a[i].v;
    }
    sort(b+1,b+m+1);
    int lb=unique(b+1,b+m+1)-b-1;
    for(reg i=1;i<=m;++i)
        a[i].v=lower_bound(b+1,b+lb+1,a[i].v)-b;
    int l=0,r=m,mid,ans;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid))ans=mid,l=mid+1;
        else r=mid-1;
    }
    if(ans==m)ans=-1;
    write(ans+1),puts("");
}
int main(){
    solve();
    return 0;
}
posted @ 2022-02-20 15:59  Muel_imj  阅读(35)  评论(0编辑  收藏  举报