P4137 Rmq Problem / mex

知识点:线段树, 离线操作

原题面

题目要求 :

给定一长度为 \(n\) 的序列 \(a\) ,
给定 \(m\) 次询问 , 每次询问 给定一区间, 询问 "区间内最小的 未出现的 自然数"

分析题意 :

  • 2019-12-20前 莫队可直接卡过

莫队太不优美 , 有一种 \(O((n + m)\log n)\) 的优美做法

  • 对于区间 \([l, l] \sim [l, n]\) 中的答案 ,
    左端点固定 , 随着右端点 右移 , 会有更多的数加入到 区间中
    显然 , 区间 \([l, l] \sim [l, n]\) 中 答案单调不递减

  • 假设已知 区间 \([l, r]\) 内的答案, 欲推得 \([l + 1, r]\) 区间内的答案 :

    • 若 区间 \([l + 1, r]\) 内不存在 数 \(a[l]\) , 且原答案 \(> a[l]\)
      则 区间 \([l + 1, r]\) 内答案可更新为 \(a[l]\)

    • 由上可得 ,
      将 左端点右移 \(1\) 后, 会被影响的区间中 必然不存在 数 \(a[l]\)
      显然, 被影响的区间为 : \([l + 1, l + 1] \sim [l + 1, l + next[l] - 1]\)

      • 其中, \(next[l]\) 代表 \(a[l]\)\([l + 1, n]\) 中第一个 出现的位置
    • 若已知 区间 \([l, l] \sim [l, n]\) 中的答案,
      且预处理出 上述的 \(next\) 数组,
      即可 通过取最小值的方式 , 来获得 区间 \([l + 1, l + 1] \sim [l + 1, l + next[l] - 1]\) 中的答案

  • 若按照上述方法 , 通过左端点右移, 来求得 不同区间答案的方法
    需要满足 询问左端点单调不降 .
    则可 离线处理询问 , 记录并排序后 , 再回答询问

算法实现:

由上 , 需要一种 支持查询 动态区间最小值 , 区间修改的数据结构
可使用 线段树进行维护

  • 按照上述要求 , 则线段树的叶节点 存储的答案 , 表示以对应点作为右端点的答案.
    即: [当前左端点 \(\sim\) 叶节点维护点] 的答案 ,

  • 对于全局左端点 左移, 获得 区间 \([l + 1, l + 1] \sim [l + 1, l + next[l] - 1]\) 中的答案
    有两种处理方法 :
    1.由于 区间 \([l + 1, l] \sim [l + 1, n]\) 中 答案单调不递减
    则可以进行线段树二分 , 求得第一个答案 \(> a[l]\) 的位置
    并将之后的位置进行修改

    2.个人的 \(sb\) 做法:
    对每一个区间, 维护其中 最大的答案 及 最小的答案

    1. 若更新值 \(<\) 最小答案时, 将整个区间进行更新
    2. 若不满足\(1\) , 若更新值 \(>\) 最大值 , 则整个区间都不需要更新 , 直接 \(return;\)
    3. 若不满足 \(1,2\) , 继续向下深入
  • 每次查询时 , 先将当前全局 左端点右移至查询左端点 ,
    再直接查询 对应右端点储存的答案即可

单次左端点右移 与查询 复杂度都为 \(O(\log n)\), 则总复杂度为 \(O((n + m)\log n)\)


#include <cstdio>
#include <algorithm>
#include <map>
#include <ctype.h>
#define max(a,b) (a > b ? a : b)
#define min(a,b) (a < b ? a : b)
#define ls (now << 1)
#define rs (now << 1 | 1)
const int MARX = 2e5 + 10;
//=============================================================
struct node
{
	int L, R;
	int ans;//记录区间答案(若区间答案不一致则为-1 
	int minn, maxx;//记录区间内 最小 / 最大 的答案 
}tree[MARX << 2];
struct Query
{
	int l, r, data;//询问的左右端点, 及询问编号 
}q[MARX];
int N, M, original[MARX], next[MARX], ans[MARX];
std :: map <int, int> last;//预处理使用, 记录某一个数 上一次出现的位置 
std:: map <int,bool> vis; //预处理使用 , 记录一个数是否出现过 
//=============================================================
inline int read()
{
    int s = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0');
    return s * w;
}
bool cmp(Query fir, Query sec)//对查询排序的 比较函数 
{
	if(fir.l == sec.l) return fir.r < sec.r;
	return fir.l < sec.l;
}
void Pushdown(int now)//下传 懒标记
{
	tree[ls].ans = tree[rs].ans = tree[now].ans;
	tree[ls].minn = tree[rs].minn = tree[now].ans;
	tree[ls].maxx = tree[rs].maxx = tree[now].ans;
	tree[now].ans = -1;
}
void Pushup(int now)//更新 now点的信息 
{
	tree[now].minn = min(tree[ls].minn, tree[rs].minn);
	tree[now].maxx = max(tree[ls].maxx, tree[rs].maxx);
	tree[now].ans = tree[ls].ans == tree[rs].ans ? tree[ls].ans : -1;//更新懒标记 
}
void Build(int now, int L, int R)//建树 
{
	tree[now].L = L, tree[now].R = R, tree[now].ans = -1;//赋初值 
	if(L == R) //递归到叶节点 
	{
	  tree[now].ans = tree[now].minn = tree[now].maxx = ans[L]; 
	  return ;
	}
	int mid = (L + R) >> 1;
	Build(ls, L, mid), Build(rs, mid + 1, R);
	Pushup(now);
}
void Change(int now, int L, int R, int key)//将 [L,R]内 答案 > key的区间 更新为key 
{
	if(L <= tree[now].L && tree[now].R <= R)//区间被完全包括 
	  if(tree[now].minn >= key)//最小值大于key, 则区间需要全部被更新 
	  {
	  	tree[now].ans = tree[now].minn = tree[now].maxx = key; 
		return ;
	  }
	  else if(tree[now].maxx <= key) return ;//最大值 小于等于key, 则区间不需要被更新 
	 
	if(tree[now].ans != -1) Pushdown(now);//下传懒标记 
	int mid = (tree[now].L + tree[now].R) >> 1;
	if(L <= mid) Change(ls, L, R, key);//递归 修改左右区间 
	if(R > mid) Change(rs, L, R, key);
	Pushup(now);//更新 now点的信息 
}
int Query(int now, int pos)//单点查询 pos的答案 
{
	if(pos == tree[now].L && tree[now].R == pos) return tree[now].ans; //寻找到答案 
	
	if(tree[now].ans != -1) Pushdown(now);//下传懒标记 
	int mid = (tree[now].L + tree[now].R) >> 1; 
	if(pos <= mid) return Query(ls, pos);//查询 对应位置 
	else return Query(rs, pos);
}
void Prepare()//预处理 
{
	N = read(), M = read();
	for (int i = 1, minn = 0; i <= N; i ++) //读入原始数列, 预处理next值 
	{
	  original[i] = read(), next[i] = N + 1;  
	  next[last[original[i]]] = i, last[original[i]] = i; //更新 next, last值 
	  vis[original[i]] = 1; //更新 此数的出现情况 
	  while(vis[minn]) minn ++; //计算 [1,i] 的答案 
	  ans[i] = minn;
	}
	Build(1, 1, N); 
	for(int i = 1; i <= M; i ++) q[i].l = read(), q[i].r = read(), q[i].data = i;
	std :: sort(q + 1, q + M + 1, cmp);//将查询按照左端点 升序排序 
}
//=============================================================
signed main()
{
	Prepare();
	for(int i = 1, nowl = 1; i <= M; i ++)//枚举 询问 
	{
	  //将左端点右移至 当前询问的左端点 
	  for(;q[i].l > nowl; nowl ++) Change(1, nowl , next[nowl] - 1, original[nowl]);
	  ans[q[i].data] = Query(1, q[i].r);//回答询问 
	}
	
	for(int i = 1; i <= M; i ++) printf("%d\n", ans[i]);
}

莫队:
查询区间未出现的 自然数
删除数时 直接更新出现次数, 并依照大小关系更新 答案
添加数时, 先更新出现次数, 之后暴力枚举查询 最小的未出现的自然数

#include <cstdio>
#include <cctype>
#include <cmath>
#include <algorithm>
#define ll long long
const int MARX = 2e5 + 10;
//===========================================================
struct Query
{
	int L, R, ID;
} Q[MARX];
int N, M, Number[MARX], Belong[MARX];
int nowl = 1, nowr, nowans, use[MARX], Ans[MARX];
//===========================================================
inline int read()
{
	int w = 0, f = 1; char ch = getchar();
	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
	for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
	return f * w;
}
bool Sort_Compare(Query first, Query second)
{
	if(Belong[first.L] == Belong[second.L]) return first.R < second.R;
	return first.L < second.L;
}
void Prepare()
{
	N = read(), M = read();
	for(int i = 1; i <= N; i ++) Number[i] = read();
	for(int i = 1; i <= M; i ++) Q[i].L = read(), Q[i].R = read(), Q[i].ID = i;
	for(int i = 1, Block = sqrt(N + 1); i <= N; i ++) Belong[i] = (i - 1) / Block + 1;
	std :: sort(Q + 1, Q + M + 1, Sort_Compare);
}
void add(int now) 
{
	use[Number[now]] ++;
	if(Number[now] == nowans)
	  while(use[nowans]) nowans ++; 
}
void del(int now) 
{
	use[Number[now]] --;
	if(! use[Number[now]] && Number[now] < nowans) 
	  nowans = Number[now];
}
//===========================================================
int main()
{
	Prepare();
	for(int i = 1; i <= M; i ++)
	{
	  while(nowl < Q[i].L) del(nowl), nowl ++;
	  while(nowl > Q[i].L) nowl --, add(nowl);
	  while(nowr > Q[i].R) del(nowr), nowr --;
	  while(nowr < Q[i].R) nowr ++, add(nowr);
	  Ans[Q[i].ID] = nowans;
	}
	for(int i = 1; i <= M; i ++) printf("%d\n", Ans[i]);
	return 0;
}

posted @ 2019-10-27 10:05  Luckyblock  阅读(117)  评论(0编辑  收藏  举报