NBOJv2 1004 蛤玮打扫教室(线段树区间更新区间最值查询)

32 篇文章 0 订阅
11 篇文章 0 订阅

Problem 1004: 蛤玮打扫教室


Time Limits:  1000 MS   Memory Limits:  65536 KB

64-bit interger IO format:  %lld   Java class name:  Main


Description

现在知道一共有n个机房,算上蛤玮一共有m个队员,教练做了m个签,每个签上写着两个数L,R(L<=R),抽到的人要把[L,R]的教室全部打扫一遍.由于蛤玮是队长而且他很懒,他通过某种交易提前知道了所有m个签上面写的是什么,而且通过某种魔法可以控制自己抽到哪个签.一个教室被打扫一次就干净了,所以蛤玮想知道自己抽哪些签可以不用打扫教室而且不会被教练发现,即他抽到的区间全都会被别人打扫一遍.

蛤玮被教练叫去打扫机房,集训队有很多机房,也有很多队员,现在他们要用抽签的方式决定谁打扫哪间教室.

 

Input

第一行为一个整数T(1<=T<=20),代表数据组数。每组数据第一行n,m(1<=n,m<=100000),接下来m行,每行两个数L,R(1<=L<=R<=n).

 

Output

每组数据输出一个k,表示多少个签符合蛤玮的要求,接下来一行输出k个数,这些签的编号,下标从1开始.

 

Sample Input

3
15 5
1 4
5 5
6 8
9 10
5 6
3 6
1 1
1 1
2 2
2 2
3 3
3 3
10 3
1 4
2 6
6 10 

Output for Sample Input

2
2 5
6 1 2 3 4 5 6
0

最近学了线段树,把以前的题目都拿出来再做了一遍,虽然说时间并比离散化的做法慢很多……主要是熟悉熟悉,这题用线段树就是区间更新,区间最(小)值查询,因为一个区间只有被至少打扫两次才能被选入,也就是选某些区间的最小值大于等于2的即可,pushdown函数稍微修改一下就好了

代码:

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<sstream>
#include<cstring>
#include<cstdio>
#include<string>
#include<deque>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>
using namespace std;
#define INF 0x3f3f3f3f
#define MM(x,y) memset(x,y,sizeof(x))
#define LC(x) (x<<1)
#define RC(x) ((x<<1)+1)
#define MID(x,y) ((x+y)>>1)
typedef pair<int,int> pii;
typedef long long LL;
const double PI=acos(-1.0);
const int N=100010;
struct info
{
	int l,mid,r;
	int maxm,add;
};
info T[N<<2];
void pushup(int k)
{
	T[k].maxm=min(T[LC(k)].maxm,T[RC(k)].maxm);
}
void pushdown(int k)
{
	T[LC(k)].add+=T[k].add;
	T[RC(k)].add+=T[k].add;
	T[LC(k)].maxm+=T[k].add;
	T[RC(k)].maxm+=T[k].add;
	T[k].add=0;
}
void build(int k,int l,int r)
{
	T[k].l=l;
	T[k].r=r;
	T[k].mid=MID(l,r);
	T[k].add=0;
	T[k].maxm=0;
	if(l==r)
		return ;
	build(LC(k),l,T[k].mid);
	build(RC(k),T[k].mid+1,r);
}
void update(int k,int l,int r,int v)
{
	if(r<T[k].l||l>T[k].r)
		return ;
	if(l<=T[k].l&&r>=T[k].r)
	{
		T[k].add+=v;
		T[k].maxm+=v;
	}	
	else
	{
		if(T[k].add)
			pushdown(k);
		update(LC(k),l,r,v);
		update(RC(k),l,r,v);
		pushup(k);
	}
}
int query(int k,int l,int r)
{
	if(l<=T[k].l&&r>=T[k].r)
		return T[k].maxm;
	if(T[k].add)
		pushdown(k);
	if(r<=T[k].mid)
		return query(LC(k),l,r);
	else if(l>T[k].mid)
		return query(RC(k),l,r);
	else
		return min(query(LC(k),l,T[k].mid),query(RC(k),T[k].mid+1,r));
}
pii seg[N];
int main(void)
{
	int tcase,n,m,l,r,i,j,cnt;
	scanf("%d",&tcase);
	while (tcase--)
	{
		scanf("%d%d",&n,&m);
		build(1,1,n);
		for (i=1; i<=m; i++)
		{
			scanf("%d%d",&seg[i].first,&seg[i].second);
			update(1,seg[i].first,seg[i].second,1);
		}
		vector<int>r;
		for (i=1; i<=m; i++)
		{
			int m=query(1,seg[i].first,seg[i].second);
			if(m>=2)
				r.push_back(i);
		}
		cnt=r.size();
		printf("%d\n",cnt);
		for (i=0; i<cnt; i++)
			printf("%d%s",r[i],i==cnt-1?"\n":" ");
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个用线段区间的示例代码,以查询区间最小为例: ```cpp #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 100005; int n, m; int a[MAXN]; struct Node { int l, r, minn; } tr[MAXN*4]; // 建立线段 void build(int u, int l, int r) { tr[u].l = l, tr[u].r = r; if (l == r) { // 叶子节点 tr[u].minn = a[l]; return; } int mid = (l + r) >> 1; build(u<<1, l, mid); build(u<<1|1, mid+1, r); tr[u].minn = min(tr[u<<1].minn, tr[u<<1|1].minn); } // 查询区间最小 int query(int u, int ql, int qr) { if (ql <= tr[u].l && tr[u].r <= qr) { // 完全包含 return tr[u].minn; } int mid = (tr[u].l + tr[u].r) >> 1; int res = 0x3f3f3f3f; // 初始化为一个极大 if (ql <= mid) res = min(res, query(u<<1, ql, qr)); // 左子区间有交集 if (qr > mid) res = min(res, query(u<<1|1, ql, qr)); // 右子区间有交集 return res; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); } build(1, 1, n); // 建立线段 while (m--) { int l, r; scanf("%d%d", &l, &r); printf("%d\n", query(1, l, r)); } return 0; } ``` 这段代码中,建立线段的过程采用递归方式实现,查询区间最小的过程也采用递归方式实现。对于每个节点,维护该节点对应区间的最小查询区间最小的过程,采用线段的递归查询方式,将查询区间划分成若干个离散的单元,并依次查询每个单元,最终得到查询区间的最小

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值