码迷,mamicode.com
首页 > 其他好文 > 详细

题解 CF997E 【Good Subsegments】

时间:2020-07-26 22:48:58      阅读:64      评论:0      收藏:0      [点我收藏+]

标签:rom   可以转化   ||   需要   lin   problem   operator   ref   git   

可以先做一下弱化版:CF526F Pudding Monsters,那道题是本题的基础。

由于这是个排列,因此好区间可以转化为满足 \(max - min = r - l\) 的区间。其中 \(max,min\) 分别表示区间最大值和最小值,\(l,r\) 分别表示区间左右端点。我们可以枚举 \(r\),那么限制变为 \(max - min + l = r\)。又因为对于所有区间,都有 \(max - min >= r - l\),所以好区间可以转化为“以 \(r\) 为右端点,且 \(max - min + l\) 最小左端点的个数”。

我们从左往右扫一遍,用单调栈和线段树维护最值,扫的时候顺便统计一下全局最小值个数,这就是前面那道题的做法。

对于这道题,我们可以离线,将询问挂到右端点,“子区间”转化为“前缀的后缀”。如果只考虑一个前缀的所有后缀的答案,这题只不过多限制左端点的范围,不能小于 \(L\),这个好说,查全局最小值个数改为查区间最小值个数即可。再考虑所有前缀的贡献,这个可以直接在线段树上打“历史贡献”的标记,查询就把节点的“历史贡献”加和即可。

总结一下,我们需要一棵线段树,支持区间加,单点修改,区间查历史贡献。还需要俩单调栈,维护最大最小值,并在线段树上进行操作。

细节看代码吧。

\(Code:\)

#define N 201000
#define NN 801000
#define int long long
template <typename T> inline void read(T &x) {
	x = 0; char c = getchar(); bool flag = false;
	while (!isdigit(c)) { if (c == ‘-‘)	flag = true; c = getchar(); }
	while (isdigit(c)) { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); }
	if (flag)	x = -x;
}
using namespace std;
const int inf = 987654321;
int n;
int h[N];
struct edge{
	int nxt, to, id;
}e[N];
int head[N], ecnt;
inline void addedge(int from, int to, int id) {//邻接表挂询问
	e[++ecnt] = (edge){head[from], to, id};
	head[from] = ecnt;
}
int ans[N];

struct node {
	int mn, cnt;
	node(int mnn = inf, int cntt = 0) { mn = mnn, cnt = cntt; }
	node operator +(const node a) const {
		return node(min(mn, a.mn), mn == a.mn ? cnt + a.cnt : (mn < a.mn ? cnt : a.cnt));
	}
}nd[NN];
int ls[NN], rs[NN], atag[NN], ctag[NN], res[NN], root, ttot;
void build(int L, int R, int &cur) {
	cur = ++ttot;
	if (L == R)	return ;
	int mid = (L + R) >> 1;
	build(L, mid, ls[cur]);
	build(mid + 1, R, rs[cur]);
}
inline void pushup(int cur) {
	nd[cur] = nd[ls[cur]] + nd[rs[cur]];
}
inline void pusha(int cur, int v) {//打加法标记
	if (!cur)	return ;
	nd[cur].mn += v;
	atag[cur] += v;
}
inline void pushc(int cur, int mn, int c) {//打历史标记
	if (!cur || nd[cur].mn != mn)	return ;
    //只有儿子最小值和父亲相同时才能继承贡献
	res[cur] += nd[cur].cnt * c;
	ctag[cur] += c;
}
inline void pushdown(int cur) {//下放标记。注意顺序
	if (atag[cur])
		pusha(ls[cur], atag[cur]), pusha(rs[cur], atag[cur]), atag[cur] = 0;
	if (ctag[cur])
		pushc(ls[cur], nd[cur].mn, ctag[cur]), 
		pushc(rs[cur], nd[cur].mn, ctag[cur]), 
		ctag[cur] = 0;
}
void modify(int L, int R, int l, int r, int v, int cur) {//区间加
	if (l <= L && R <= r) {
		pusha(cur, v);
		return ;
	}
	pushdown(cur);
	int mid = (L + R) >> 1;
	if (l <= mid)	modify(L, mid, l, r, v, ls[cur]);
	if (r > mid)	modify(mid + 1, R, l, r, v, rs[cur]);
	pushup(cur);
}
void modify(int L, int R, int pos, int v, int cur) {//单点修改
	if (L == R)	return nd[cur] = (node){v, 1}, void();
	pushdown(cur);
	int mid = (L + R) >> 1;
	if (pos <= mid)	modify(L, mid, pos, v, ls[cur]);
	else	modify(mid + 1, R, pos, v, rs[cur]);
	pushup(cur);
}
int query(int L, int R, int l, int r, int cur) {
	if (l <= L && R <= r)	return res[cur];
	pushdown(cur);
	int mid = (L + R) >> 1, tmp = 0;
	if (l <= mid)	tmp = query(L, mid, l, r, ls[cur]);
	if (r > mid)	tmp += query(mid + 1, R, l, r, rs[cur]);
	return tmp;
}

struct Seg {//单调栈
	int l, r, v;//l ~ r 的最值都是 v
	Seg(int ll = 0, int rr = 0, int vv = 0) { l = ll, r = rr, v = vv; }
	bool operator <(const Seg a) const {
		return v < a.v;
	}
	bool operator >(const Seg a) const {
		return v > a.v;
	}
}smx[N], smn[N];
int mxtop, mntop;

signed main() {
	read(n);
	for (register int i = 1; i <= n; ++i)	read(h[i]);
	int q; read(q);
	for (register int i = 1; i <= q; ++i) {
		int l, r; read(l), read(r);
		addedge(r, l, i);
	}
	build(1, n, root);
	for (register int i = 1; i <= n; ++i) {
    	//维护最值
		Seg s(i, i, h[i]);
		while (mxtop && s > smx[mxtop])
			modify(1, n, smx[mxtop].l, smx[mxtop].r, h[i] - smx[mxtop].v, root),
			s.l = smx[mxtop].l, --mxtop;
		smx[++mxtop] = s;
		
		s = Seg(i, i, h[i]);
		while (mntop && s < smn[mntop])
			modify(1, n, smn[mntop].l, smn[mntop].r, -h[i] + smn[mntop].v, root),
			s.l = smn[mntop].l, --mntop;
		smn[++mntop] = s;
		
		modify(1, n, i, i, root);//插入新点
		
		pushc(root, i, 1);//将当前的合法区间计入贡献
		for (register int j = head[i]; j; j = e[j].nxt) {
			int l = e[j].to, id = e[j].id;
			ans[id] = query(1, n, l, i, root);
		}
	}
	for (register int i = 1; i <= q; ++i)
		printf("%lld\n", ans[i]);
	return 0;
}

题解 CF997E 【Good Subsegments】

标签:rom   可以转化   ||   需要   lin   problem   operator   ref   git   

原文地址:https://www.cnblogs.com/JiaZP/p/13381983.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!