【洛谷 2898】 [USACO08JAN]haybale猜测Haybale Guessing

题目描述

The cows, who always have an inferiority complex about their intelligence, have a new guessing game to sharpen their brains.

A designated ‘Hay Cow’ hides behind the barn and creates N (1 ≤ N ≤ 1,000,000) uniquely-sized stacks (conveniently numbered 1..N) of hay bales, each with 1..1,000,000,000 bales of hay.

The other cows then ask the Hay Cow a series of Q (1 ≤ Q ≤ 25,000) questions about the the stacks, all having the same form:

What is the smallest number of bales of any stack in the range of stack numbers Ql..Qh (1 ≤ Ql ≤ N; Ql ≤ Qh ≤ N)?The Hay Cow answers each of these queries with a single integer A whose truthfulness is not guaranteed.

Help the other cows determine if the answers given by the Hay Cow are self-consistent or if certain answers contradict others.

给一段长度为n,每个位置上的数都不同的序列a[1..n]和q和问答,每个问答是(x, y, r)代表RMQ(a, x, y) = r, 要你给出最早的有矛盾的那个问答的编号。

输入输出格式

输入格式:
Line 1: Two space-separated integers: N and Q

Lines 2..Q+1: Each line contains three space-separated integers that represent a single query and its reply: Ql, Qh, and A
输出格式:
Line 1: Print the single integer 0 if there are no inconsistencies among the replies (i.e., if there exists a valid realization of the hay stacks that agrees with all Q queries). Otherwise, print the index from 1..Q of the earliest query whose answer is inconsistent with the answers to the queries before it.
输入输出样例

输入样例#1:
20 4
1 10 7
5 19 7
3 12 8
11 15 12
输出样例#1:
3
【2017.11.2考试T3】

题意

给出n个互不相同的数字,每次给出一段区间的最小值,求最早出现矛盾的询问次数。若一直不矛盾则输出0。(考试时是输出t+1)

题解

并查集+二分

二分最终答案。
mid表示前mid次出现了矛盾。若mid合法,则缩小mid。

每次check都要初始化fa数组,并且用另一个结构体数组复制一下前mid组数据,防止因为排序影响后续操作。

不合法的情况有且只有两个:
1、小区间完全被大区间覆盖;
2、两段互不相交的区间最小值相同(题目条件是n个数互不相同)。

于是先将前mid个最小值从大到小排序,每确定一段区间的最小值都对区间进行染色,如果遇到小区间完全被大区间覆盖的情况,直接返回不合法;

用并查集维护染色情况,染色操作是将整个区间合并到染色区间的右端点的下一个端点上,使区间内每个点的父亲指向右端点的下一个端点。在后面的染色过程中,一段区间的左端点的父亲在右端点的右边,说明这段区间已经全部染过色,返回不合法。

跟数轴染色一样,不能使用按秩合并。

每次check时维护四个值:lmin,lmax,rmin,rmax。

由于是从大到小排了一遍序,所以每次更新一个区间时只会出现两种情况:
1、当前最小值与上一次的最小值相等;
2、当前最小值 < 上一个最小值

分类处理即可

①当前最小值< 上一个最小值:

if(t[i].z < t[i - 1].z)
{
    if(find(lmax) > rmin) return 1;//区间被完全覆盖过
    for(int j = find(lmin); j <= rmax; j++)//区间没有被完全覆盖 
    f[find(j)]=find(rmax + 1);
    lmin=lmax=t[i].x;
    rmin=rmax=t[i].y;
}

区间没有被完全覆盖时,又分为两种情况:
区间完全没有被覆盖
一部分被覆盖
两种情况在处理时一起处理,进行染色合并的条件是当前点没有被覆盖过。
只要一段区间没有被完全覆盖,那么就可以也只能对其没有被覆盖的部分染色。
find(lmin)即为区间内没有被覆盖的子区间的开端,从这里开始染,染到这段区间的右端点结束。

f[find(j)]=find(rmax + 1);

其中的这个操作有很多冗余的部分。含义是对每一个j,都找到它的父亲,再将父亲指向区间右端点的下一个。实际上可能存在一个v点,j~v已经染过色,这样就对j~v内的点又染了一遍。t。

可以采用以下方法避免重复染色:

if(b[i].z<b[i-1].z)
{
    if(find(lmax)>rmin) return 1;//区间被完全覆盖过
    else for(int j=find(lmin);j<=rmax;j++)
    {
        j=find(j);
        fa[j]=find(rmax+1);
    }
    lmin=lmax=b[i].x,rmin=rmax=b[i].y;
}

使j=v+1后再对j以后的逐个染色。
这样就是1s与2s的区别了。
②:当前最小值与上一次的最小值相等
这种情况又可以分为两种情况讨论:
在最小值相等的情况下,当前区间与上一段区间可能存在重合的部分,也可能不存在重合的部分。
不存在重合的部分时,返回不合法;
判断过程:

else if(b[i].z==b[i-1].z)
{
    lmin=min(lmin,b[i].x);
    lmax=max(lmax,b[i].x);
    rmin=min(rmin,b[i].y);
    rmax=max(rmax,b[i].y);
    if(lmax>rmin) return 1;
}

大概就是这样:
这里写图片描述
存在重合的部分时,按理说应该是合法的,但是往往容易忽略一种情况,就是重合的部分被完全覆盖过。这种情况上面判不出来。
是留到下一回合的时候判:

if(find(lmax)>rmin) return 1;

这样这个题就完了。
完整代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1000000+10;
int fa[maxn];
int n,t;
struct node
{
    int x,y,z;
}a[maxn],b[maxn];
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
    for(; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    return x * f;
}
bool cmp(node x,node y)
{
    return x.z>y.z;
}
void init(int n)
{
    for(int i=1;i<=n+1;i++)
    fa[i]=i;
}
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
bool check(int k)
{
    init(n);
    for(int i=1;i<=k;i++) b[i]=a[i];
    sort(b+1,b+k+1,cmp);
    int lmin,lmax,rmin,rmax;
    lmin=lmax=b[1].x,rmin=rmax=b[1].y;
    for(int i=2;i<=k;i++)
    {
        if(b[i].z<b[i-1].z)
        {
            if(find(lmax)>rmin) return 1;//区间被完全覆盖过
            else for(int j=find(lmin);j<=rmax;j++)
            {
                j=find(j);
                fa[j]=find(rmax+1);
            }
            lmin=lmax=b[i].x,rmin=rmax=b[i].y;
        }
        else if(b[i].z==b[i-1].z)
        {
            lmin=min(lmin,b[i].x);
            lmax=max(lmax,b[i].x);
            rmin=min(rmin,b[i].y);
            rmax=max(rmax,b[i].y);
            if(lmax>rmin) return 1;
        }
    }
    if(find(lmax)>rmin) return 1;
    return 0;
}
int main()
{
    n=read(),t=read();
    init(n);
    for(int i=1;i<=t;i++)
    a[i].x=read(),a[i].y=read(),a[i].z=read();
    int l=1,r=t,ans=t+1;
    while(r>=l)
    {
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    if(ans!=t+1)
    cout<<ans<<'\n';
    else puts("0");
    return 0;
}

总结

1、考试的时候想到的是每给出一段区间的最小值,如果合法,就把这一整段区间全部赋值为给出的这个最小值,在赋值的过程中我不仅给单点赋值,还给在当前更新区间范围内的每一段没更新过的区间或最小值比当前大的区间都赋了值,然而并不能确定每个点的值具体是多少,考虑的时候也是单点考虑,并没有想到合并,对于冲突的判断也是奇奇怪怪的,而且没有利用n个数互不相等的条件,所以无论怎么改,最多只能过四个小数据;
2、关于二分:
二分常见写法有两个:

while(r>=l)
{
    int mid=(l+r)>>1;
    if(check(mid)) ans=mid,r=mid-1;
    else l=mid+1;
}
while(r-l>1)
{
    int mid=(l+r)>>1;
    if(check(mid)) ans=mid,r=mid;
    else l=mid;
}

觉得第二个好看
3、check的时候,判断的对象和染色的对象是之前的区间。如果当前区间与之前的区间不矛盾,那么把之前的区间染色。所以最后要在while外面加一句对于i=k时的判断:

if(find(lmax)>rmin) return 1;

检查当前最小值与前一个最小值相等并且有重合部分时,重合部分是否被覆盖过是在当前的下一个回合进行检查。如果当前已经=k,那么在while后面还有一句判断,不用担心有漏网之鱼~
4、在没有完全理解的时候,不要去随便质疑标程。
高高兴兴地将标程脱胎换骨后才知道删掉的变量是有用的,删掉之后没法判断出当前最小值与前一个最小值相等并且有重合部分时,重合部分被覆盖过的情况。
改错的程序放在这~

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1000000+10;
int fa[maxn],n,t;
struct node
{
    int x,y,z;
}a[maxn],b[maxn];
void init(int n)
{
    for(int i=1;i<=n+1;i++) fa[i]=i;
}
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
bool cmp(node a,node b)
{
    return a.z>b.z;
}
bool check(int k)
{
    init(n);
    for(int i=1;i<=k;i++) b[i]=a[i];
    sort(b+1,b+k+1,cmp);
    for(int i=2;i<=k;i++)
    {
        if(b[i].z<b[i-1].z)
        {
            if(find(b[i-1].x)>b[i-1].y) return 1;
            for(int j=find(b[i-1].x);j<=b[i-1].y;j++)
            fa[find(j)]=find(b[i-1].y+1);
        }
        else if(b[i].z==b[i-1].z&&max(find(b[i].x),find(b[i-1].x))>min(b[i].y,b[i-1].y)) return 1;
    }
    if(find(b[k].x)>b[k].y) return 1;
    return 0;
}
int main()
{
    scanf("%d%d",&n,&t);
    for(int i=1;i<=t;i++)
    scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
    int l=0,r=t,ans=t+1;
    while(r>=l)
    {
        int mid=(l+r)>>1;
        if(check(mid))//遇到了矛盾
        r=mid-1,ans=mid;
        else l=mid+1;
    }
    cout<<ans<<'\n';
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一道经典的单调栈问题。题目描述如下: 有 $n$ 个湖,第 $i$ 个湖有一个高度 $h_i$。现在要在这些湖之间挖一些沟渠,使得相邻的湖之间的高度差不超过 $d$。请问最少需要挖多少个沟渠。 这是一道单调栈的典型应用题。我们可以从左到右遍历湖的高度,同时使用一个单调栈来维护之前所有湖的高度。具体来说,我们维护一个单调递增的栈,栈中存储的是湖的下标。假设当前遍历到第 $i$ 个湖,我们需要在之前的湖中找到一个高度最接近 $h_i$ 且高度不超过 $h_i-d$ 的湖,然后从这个湖到第 $i$ 个湖之间挖一条沟渠。具体的实现可以参考下面的代码: ```c++ #include <cstdio> #include <stack> using namespace std; const int N = 100010; int n, d; int h[N]; stack<int> stk; int main() { scanf("%d%d", &n, &d); for (int i = 1; i <= n; i++) scanf("%d", &h[i]); int ans = 0; for (int i = 1; i <= n; i++) { while (!stk.empty() && h[stk.top()] <= h[i] - d) stk.pop(); if (!stk.empty()) ans++; stk.push(i); } printf("%d\n", ans); return 0; } ``` 这里的关键在于,当我们遍历到第 $i$ 个湖时,所有比 $h_i-d$ 小的湖都可以被舍弃,因为它们不可能成为第 $i$ 个湖的前驱。因此,我们可以不断地从栈顶弹出比 $h_i-d$ 小的湖,直到栈顶的湖高度大于 $h_i-d$,然后将 $i$ 入栈。这样,栈中存储的就是当前 $h_i$ 左边所有高度不超过 $h_i-d$ 的湖,栈顶元素就是最靠近 $h_i$ 且高度不超过 $h_i-d$ 的湖。如果栈不为空,说明找到了一个前驱湖,答案加一。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值