洛谷P2898haybale猜测Haybale Guessing

https://www.luogu.org/problemnew/show/2898

思路
显然,若按照题目给定的顺序处理,判断过程将异常麻烦….
但仔细分析题目后发现,本题对矛盾的产生有着诸多限制在。
那么我们可以考虑,对于矛盾的情况下一个统一定义
对于本题,则为“若某一确定了最小值的区间,被一个或多个最小值比他大的区间完全覆盖,那么此时的情况就是矛盾的。”—— 注意适当的将此种方法扩展到其他题目,使给定信息变得规律而易于处理。
由此我们就可以二分产生矛盾的第一个位置,将该位置及以前的猜测条件按猜测最小值从大到小排序后依次判断验证——因为顺序并不会影响矛盾的产生。

如何判断呢?

方法一:并查集

用类似数轴染色的方法进行区间染色,具体参考:
http://blog.youkuaiyun.com/qq_36693533/article/details/78428668
然后就是部分十分关键的细节处理问题:

1.我们设置lmax,lmin,rmax,rmin记录两个颜色(最小值)相同的区间中,较大,较小的左端点和较大,较小的右端点。则lmax-rmin为区间的交集(颜色确定),lmin-rmax为区间的并集(颜色未确定但其中不可完全包含之后最小值更小的区间)。,这两个范围对矛盾的产生都有影响所以都需要染色
另外,我们是通过交集范围是否在之前被染过色判断是否合法(1),和通过后面的区间是否被并集完全包含判断是否合法的(2)。请仔细想一下。

2.因为题目保证没有相同的数字,那么两个颜色相同的区间必定要存在交集,否则不合法。


3.对颜色相同的区间,我们只会更新其并集和交集范围,而要等到颜色相同的区间全部读完了,即等到我们读到一个颜色不同的区间为止,我们才会去验证之前颜色相同区间确定的并集范围有没有被更更早的区间染过色,这样一定是不合法的(矛盾定义)。(1),若合法则进行区间染色。

4.当我们读入一个与之前颜色不同区间后,我们并不知道其后是否有与之颜色相同的区间。方便起见,我们将lmax,lmin设为区间左端点,rmax,rmin设为区间右端点。
这样若之后的区间与之颜色相同,那么lmax,lmin,rmax,rmin都会被更新无影响。
若之后的区间与之颜色不同,那么我们对于交集的判断语句其实就成了判断前一个区间范围有没有被更更早的区间染过色,即判断当前区间有没有被之前(显然最小值较大)的区间完全包含。 (2)
关于不同的情况,需要注意的是,我们的判断是:如果区间范围完全被染过色才为非法,即我们之前读入的区间信息相对于当前信息为非法。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,t,ans,lz,rz;
int lmin,lmax,rmin,rmax;
int fa[1000010];
struct inte
{
    int ls,rs,mins;
}p[1000010];
inte tmp[1000010];
bool cmp(inte a,inte b)
{
    return a.mins>b.mins;
}
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
int check(int k)
{
    for(int i=1;i<=n+1;i++)//验证前k次判断对整个1-n区间的影响 
    fa[i]=i;               //数轴染色并查集的思路,若某一染色区间右端点为n,则将区间所以元素连至n+1下 
    for(int i=1;i<=k;i++)
    tmp[i]=p[i];
    sort(tmp+1,tmp+k+1,cmp);//从大到小排序
    lmax=lmin=tmp[1].ls;
    rmax=rmin=tmp[1].rs; 
    for(int i=2;i<=k;i++)
    {
        if(tmp[i].mins==tmp[i-1].mins)
        {
            lmin=min(lmin,tmp[i].ls);//更新 
            lmax=max(lmax,tmp[i].ls);
            rmin=min(rmin,tmp[i].rs);
            rmax=max(rmax,tmp[i].rs);
            if(lmax>rmin) return 1;//若最小值相同的区域没有交集 
        }
        else
        {
            if(find(lmax)>rmin)//当前区间已经被染过色,即当前最小值较小区间被最小值较大的区间完全覆盖 
            return 1;          //或之前的相同颜色区间的交集被染过色 
            for(int j=find(lmin);j<=rmax;j++)//与数轴染色不同,同一位置可能被多次染不同的颜色 
            {                                //用区间交集判断是否矛盾,但需将区间并集染色 
                fa[find(j)]=find(rmax+1);
            }
            lmin=lmax=tmp[i].ls;//方便处理 
            rmin=rmax=tmp[i].rs;
        }
    }
    if(find(lmax)>rmin)
    return 1;//判断最后一次 
    return 0;
}
int main()
{
    scanf("%d%d",&n,&t);
    for(int i=1;i<=t;i++)
    scanf("%d%d%d",&p[i].ls,&p[i].rs,&p[i].mins);
    lz=1;
    rz=t;
    ans=0;//一直没出现矛盾输出0 
    while(lz<=rz)//根据具体题目调整二分细节 
    {
        int mid=(lz+rz)>>1;
        if(check(mid))//若mid及mid之前存在矛盾,则已知当前的最小答案为mid,记录然后尝试缩小答案 
        {
            ans=mid;
            rz=mid-1;
        }
        else lz=mid+1;
    }
    printf("%d",ans);
    return 0; 
}

方法二:线段树

思路参考并查集讲解,只是将并查集的区间染色换为了线段树的区间染色。
注意在本题中我们只维护区间是否被染过色即可。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,t,ans,lz,rz;
int lmin,lmax,rmin,rmax;
struct inte
{
    int ls,rs,mins;
}p[1000010];
struct inie
{
    int l,r;
}tree[4000010];
int sum[4000010],add[4000010];//区间染色个数,标记,开与tree一样的空间 
inte tmp[1000010];
bool cmp(inte a,inte b)
{
    return a.mins>b.mins;
}
void pushdown(int now)
{
    if(add[now])
    {
        sum[now<<1]=(tree[now<<1].r-tree[now<<1].l+1);
        sum[now<<1|1]=(tree[now<<1|1].r-tree[now<<1|1].l+1);
        add[now<<1]=1;
        add[now<<1|1]=1;
        add[now]=0;
    }
}
void update(int now)
{
    sum[now]=sum[now<<1]+sum[now<<1|1]; 
}
void build(int now,int l,int r)
{
    tree[now].l=l;
    tree[now].r=r;
    if(l==r)
    return;
    int mid=(l+r)>>1;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
}
void change(int now,int l,int r)
{
    if(sum[now]==(tree[now].r-tree[now].l+1))//染过色的区间无需再染,减少递归次数 
    return; 
    if(tree[now].l>=l&&tree[now].r<=r)
    {
        sum[now]=tree[now].r-tree[now].l+1;
        add[now]=1;
        return;
    }
    pushdown(now);
    int mid=(tree[now].l+tree[now].r)>>1;
    if(l<=mid)
    change(now<<1,l,r);
    if(r>mid)
    change(now<<1|1,l,r);
    update(now);
}
int ask(int now,int l,int r)
{
    if(tree[now].l>=l&&tree[now].r<=r)
    return sum[now];
    pushdown(now);
    int ans=0;
    int mid=(tree[now].l+tree[now].r)>>1;
    if(l<=mid)
    ans+=ask(now<<1,l,r);
    if(r>mid)
    ans+=ask(now<<1|1,l,r);
    return ans; 
}
int check(int k)
{
    memset(sum,0,sizeof(sum));
    memset(add,0,sizeof(add));
    for(int i=1;i<=k;i++)
    tmp[i]=p[i];
    sort(tmp+1,tmp+k+1,cmp);
    lmax=lmin=tmp[1].ls;
    rmax=rmin=tmp[1].rs; 
    for(int i=2;i<=k;i++)
    {
        if(tmp[i].mins==tmp[i-1].mins)
        {
            lmax=max(lmax,tmp[i].ls);
            lmin=min(lmin,tmp[i].ls);
            rmax=max(rmax,tmp[i].rs);
            rmin=min(rmin,tmp[i].rs);
            if(lmax>rmin)
            return 1;
        }
        else
        {
            if(ask(1,lmax,rmin)>=(rmin-lmax+1))
            return 1; 
            change(1,lmin,rmax);
            lmax=lmin=tmp[i].ls;
            rmax=rmin=tmp[i].rs;
        }
    }
    if(ask(1,lmax,rmin)>=(rmin-lmax+1))
    return 1;
    return 0;
}
int main()
{
    scanf("%d%d",&n,&t);
    for(int i=1;i<=t;i++)
    scanf("%d%d%d",&p[i].ls,&p[i].rs,&p[i].mins);
    build(1,1,n);
    lz=1;
    rz=t;
    ans=0;
    while(lz<=rz)
    {
        int mid=(lz+rz)>>1;
        if(check(mid))
        {
            ans=mid;
            rz=mid-1;
        }
        else lz=mid+1;
    }
    printf("%d",ans);
    return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值