A
B

[Usaco2008 Jan]猜数游戏题解 ( 并查集(线段树)+二分 )

[Usaco2008 Jan]猜数游戏

并查集(线段树)+二分

Description

为了提高自己低得可怜的智商,奶牛们设计了一个新的猜数游戏,来锻炼她们的逻辑推理能力。 游戏开始前,一头指定的奶牛会在牛棚后面摆N(1 <= N<= 1,000,000)堆干草,每堆有若干捆,并且没有哪两堆中的草一样多。所有草堆排成一条直线,从左到右依次按1..N编号,每堆中草的捆数在1..1,000,000,000之间。 然后,游戏开始。另一头参与游戏的奶牛会问那头摆干草的奶牛 Q(1 <= Q <= 25,000)个问题,问题的格式如下: 编号为Ql..Qh(1 <= Ql <= Qh <= N)的草堆中,最小的那堆里有多少捆草? 对于每个问题,摆干草的奶牛回答一个数字A,但或许是不想让提问的奶牛那么容易地得到答案,又或许是她自己可能记错每堆中干草的捆数,总之,她的回答不保证是正确的。 请你帮助提问的奶牛判断一下,摆干草的奶牛的回答是否有自相矛盾之处。

Input Format

第1行: 2个用空格隔开的整数:N 和 Q
第2..Q+1行: 每行为3个用空格隔开的整数Ql、Qh、A,描述了一个问题以及它 对应的回答

Output Format

第1行: 如果摆干草的奶牛有可能完全正确地回答了这些问题(也就是说,能 找到一种使得所有回答都合理的摆放干草的方法),输出0,否则输出 1个1..Q中的数,表示这个问题的答案与它之前的那些回答有冲突之处

Sample

样例输入

20 4
1 10 7
5 19 7
3 12 8
11 15 12

输入说明:

编号为1..10的草堆中,最小的那堆里有7捆草,编号为5..19的草堆中同样
如此;编号为3..12的草堆中最小的堆里是8捆草,11..15堆中的最小的堆里是12捆。

样例输出

3

输出说明:

对于第3个问题“3 12”的回答“8”与前面两个回答冲突。因为每堆中草的
捆数唯一,从前两个回答中我们能推断出,编号为5..10的干草堆中最小的那堆
里有7捆干草。很显然,第3个问题的回答与这个推断冲突。

Hint

注意:如果有冲突出现输出一个数m,使得前M-1个命题不冲突。

正片开始

由题目可知,要求我们对于一系列区间最小值,判断第一个矛盾的最小值。

首先思考矛盾情况:

1

存在两个区间最小值相等的情况下没有交集

这只需要判断区间最左端和另一个区间最右端和区间最右端和另一个区间最左端就行了

    l = max(l,a1[i].l);
	r = min(r,a1[i].r);
	l1 = min(l1,a1[i].l);
	r1 = max(r1,a1[i].r);
    if(l > r) return false;//如果两个区间w相同,且没有交集,不符合 

2

存在最小值大的区间完全包含最小值小的区间

这个可能棘手一点(因为不只是两两判断)

---------------------------优雅的分界线----------------------------

好,现在开始思考如何A掉这个题

首先想到用暴力

暴力大法失灵力(悲)只能得6分

那么我们就从区间入手。

题目要求我们求并集和交集......
欸,怎么这么熟悉。

这不是并查集嘛

事实是用线段树也行(不过码量太大,懒得打

那刚才说的第二种情况就不棘手了,只要将这个区间的每一个值都连到区间右端的下一个值就行了

为了方便查找最小值的比较,只要用sort把每个区间最小值排一遍就行了。

但问题又来了,怎么找第一个矛盾问题呢?

如果从前往后一个一个找,不用说肯定大T特T。

有什么方法可以快速找到呢?

二分啊

那么到现在,思路已经清晰了,现在就可以愉快地A掉这个题了

下面 上代码

#include<bits/stdc++.h>
#define Blue_Archive return 0;
#define int long long
using namespace std;
const int N = 1100050;
const int INF = 0x3f3f3f3f;

int n,m,l,r;
int fa[N];//father数组 

struct miku//结构体,存问题(miku可爱捏) 
{
	int l,r,w;
}a[N],a1[N];

inline bool cmp(miku a,miku b)//排序 
{
	return a.w > b.w;
}

inline int find(int x)//并查集查找函数 
{
	return x==fa[x]?x:fa[x]=find(fa[x]);
}

inline void init()//并查集初始化函数 
{
	for(int i = 1;i <= n + 1;i ++)
	{
		fa[i] = i;
	}
}

inline bool hifumi(int x)//判断前x句话是否合法(日富美可爱捏) 
{
	int l,l1,r,r1;
	init();//初始化 
	for(int i = 1;i <= x;i ++)
	{
		a1[i] = a[i];//因为要将前x句话的w从大到小排序,所以需要一个新数组 
	} 
	sort(a1 + 1,a1 + x + 1,cmp);//快排 
	int v = a1[1].w;//标记w最大区间的左右 
	l = l1 = a1[1].l;//l标记最大的左边,l1标记最小的左边 
	r = r1 = a1[1].r;//r标记最小的右边,r1标记最大的右边 
	for(int i = 2;i <= x;i ++)
	{
		if(v != a1[i].w)
		{
			int op = find(l);
			if(op <= r)
			{
				int now = find(l1);
				while(now <= r1)
				{
					fa[now] = fa[now + 1];//遍历区间,全部连向右端点+1 
					now = find(now + 1);
				}
			}
			else return false;//这个点被最小值更大的点覆盖了,那就会有冲突 
            v = a1[i].w;//更新 
			l = l1 = a1[i].l;
			r = r1 = a1[i].r;
        }
		else
		{
            l = max(l,a1[i].l);
			r = min(r,a1[i].r);
			l1 = min(l1,a1[i].l);
			r1 = max(r1,a1[i].r);
            if(l > r) return false;//如果两个区间w相同,且没有交集,不符合 
        }
    }
	if(find(l) > r)
	{
		return false;//同理可得 
	}
	return true;
}

inline void Shiroko()//普通二分(白子可爱捏) 
{
	while(l <= r)
	{
		int mid = (l + r) >> 1;
		if(hifumi(mid))
		{
			l = mid + 1;//如果判断成立,向左查找 
		}
		else
		{
			r = mid - 1;//else 向右查找 
		}
	}
}

signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);//关闭输入输出流(不会打快读快写) 
	cin >> n;
	cin >> m;
	for(int i = 1;i <= m;i ++)
	{
		cin >> a[i].l >> a[i].r >> a[i].w;//输入问题 
	}
	l = 0;//二分左端 
	r = m;//二分右端 
	Shiroko();
	if(l > m)//没有矛盾问题 
	{
		cout << 0 << "\n";
	}
	else//l即第一个矛盾问题 
	{
		cout << l << "\n";
	}
	Blue_Archive;//诶嘿 
}
posted @ 2025-07-31 06:43  MyShiroko  阅读(2)  评论(0)    收藏  举报