参考文献:题解 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;
}