线段树 + 二分答案:Haybale Guessing G

参考文献:题解 P2898 【[USACO08JAN]haybale猜测Haybale Guessing】 - レムの小屋 - 洛谷博客

题目链接:[USACO08JAN]Haybale Guessing G - 洛谷

分析:

首先想一下矛盾的情况有哪些?

 此两种情况会产生矛盾, 

 第二条的话就判断两个无交集的区间是否有相同的最小值,有的话那么一定是错的。

第一条的话可以从大到小的方式求解区间,如果当前所求的区间在之前已经求过的话,那么这个区间之前已经确定了一个更大的值,那么当前区间和之前确定的区间之间就会存在矛盾。

同时,对于这种第一个出现问题的情况,我们可以想想看是否可以使用二分的方式得到答案。

如果后面是错的,那么我们一定可以去往前面找是否有错误,这就说明了他们是单调的。可以使用二分。

同时,了解到二分时候的l == r == mid的含义是 l,r,mid这个时候是正确的时候,所以第一个错误的情况就是 l + 1。

 代码实现:

# include <iostream>
# include <cstring>
# include <algorithm>
using namespace std;

const int N = 1e6 + 10;

int n,q;

struct tt
{
    int l,r,v;
}tr[N],tr2[N];

struct Node
{
    int l,r;
    int sum;
    int lz; // 懒标记
}edgs[N * 4];

bool cmp(struct tt a , struct tt b) // 从大到小排
{
    return a.v > b.v;
}

void pushup(int u)
{
    edgs[u].sum = edgs[2 * u].sum + edgs[2 * u + 1].sum;
}

void pushdown(int u)
{
    if(edgs[u].lz)
    {
        edgs[2 * u].lz = edgs[u].lz;
        edgs[2 * u].sum = (edgs[u * 2].r - edgs[u * 2].l + 1) * edgs[u].lz; // edgs[2 * u]的所有区间都被用过了
        edgs[2 * u +1].lz = edgs[u].lz;
        edgs[2 * u + 1].sum = (edgs[2 * u + 1].r - edgs[2 * u +1].l + 1) * edgs[u].lz;

        edgs[u].lz = 0;
    }
}

void build(int u , int l , int r)
{
    edgs[u].l = l;
    edgs[u].r = r;
    if(l == r)
    {
        return;
    }
    else
    {
        int mid = (edgs[u].l + edgs[u].r) / 2;
        build(2 * u , l ,mid);
        build(2 * u + 1 , mid + 1 , r);
        return;
    }
}

void modify(int u , int l , int r , int v)
{
    if(l <= edgs[u].l && edgs[u].r <= r)
    {
        edgs[u].sum = (edgs[u].r - edgs[u].l + 1) * v;
        edgs[u].lz = v;
    }
    else
    {
        pushdown(u);
        int mid = ( edgs[u].l + edgs[u].r ) / 2;
        if(l <= mid)
        {
            modify(2 * u , l , r , v);
        }
        if(r > mid)
        {
            modify(2 * u + 1 , l , r , v);
        }
        pushup(u);
    }
}

int query(int u , int l , int r)
{
    if(l <= edgs[u].l && edgs[u].r <= r)
    {
        return edgs[u].sum;
    }
    else
    {
        pushdown(u);
        int mid = ( edgs[u].l + edgs[u].r ) / 2;
        int ans = 0;
        if(l <= mid)
        {
            ans += query(2 * u , l , r);
        }
        if(r > mid)
        {
            ans += query(2 * u + 1 , l , r);
        }
        return ans;
    }
}

bool check(int x)
{
    memset(edgs,0,sizeof edgs);
    build(1,1,n);

    for(int i = 1 ; i <= q ; i++)
    {
        tr2[i] = tr[i];
    }

    sort(tr2 + 1 , tr2 + x + 1,cmp); // 将x个从大值到小值进行排序

    for(int i = 1 ; i <= x ; )
    {
        int j = i; // j找到第一个比i值的v小的值
        while(j <= x && tr2[j].v == tr2[i].v)
        {
            j++;
        }

        //i ~ j - 1位置上的所有值都是相等的,求这个区间的 并集 和 交集
        int ul,ur,cl,cr; // ul,ur代表者并集的左右区间,cl和cr代表着交集的左右区间
        ul = cl = tr2[i].l;
        ur = cr = tr2[i].r;

        for(int k = i; k < j ; k++)
        {
            ul = min(ul,tr2[k].l);
            ur = max(ur,tr2[k].r);
            cl = max(cl,tr2[k].l);
            cr = min(cr,tr2[k].r);
        }
        if(cr < cl) // 交集为空,说明有两个不是在一个交集的有相同的最小值,这是错误的
        {
            return false;
        }
        if(query(1,cl,cr) == cr - cl + 1) // 如果前面大值已经将cr - cl + 1 覆盖了,那么现在的小值的区间就会与前面的重复,是错误的
        {
            return false;
        }
        modify(1,ul,ur,1); // 区间修改,cl~cr区间全部改为1
        i = j;
    }
    return true;
}

int main()
{
    scanf("%d %d",&n,&q);
    for(int i = 1 ; i <= q ; i++)
    {
        int l,r,x;
        scanf("%d %d %d",&l,&r,&x);
        tr[i] = {l,r,x};
    }

    int l = 1 , r = q; // 可能q个都对
    while(l < r)
    {
        int mid = (l + r + 1) / 2;  // 第mid个说了假话
        if(check(mid)) // 如果成立的话,
        {
            l = mid;
        }
        else
        {
            r = mid - 1;
        }
    }

    // l和r为最后一个正确的
    if(r == q) // 都正确,返回0.
    {
        printf("%d\n",0);
    }
    else    printf("%d\n",r + 1);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值