【BZOJ3110】[ZJOI2013]K大数查询(整体二分)

题目:

BZOJ3110

分析:

整体二分模板题……

先明确一下题意:每个位置可以存放多个数,第一种操作是“加入 (insert) ”一个数而不是“加上 (add) ”一个数。

首先考虑只有一次询问的情况。设询问的名次为(k),我们二分出一个答案(mid),然后遍历所有修改。建立一棵区间线段树(下标是位置的线段树),对于一个给([a,b])区间加入一个数(c)的修改,如果(cgeq mid),就给([a,b])这个区间整体加(1)。最后查询询问区间的和,即这个区间中不小于(mid)的数的数量。如果这个数量大于等于(k)则向上二分,并记录答案,否则向下二分。这样单次询问的复杂度是(O(mlog_2^2n))

可以看出,询问时最耗时间的是遍历所有修改并维护线段树。而这个操作只与当前二分到的(mid)有关,与询问无关。因此考虑把所有询问放在一个集合中,每次把询问分为将要“向上二分”(答案在([mid+1,r]))和“向下二分”(答案在([l,mid])两个集合,并把可能对它们产生贡献的修改也分别加入这两个集合,然后分别递归下去。这就是“整体二分”。下面重点介绍如何对询问和修改分为两个集合。以下把修改和询问统称为“操作”。

询问的分法比较显然。如同只有一个询问的情况,把当前操作集合中的修改全部插入到线段树上。如果询问区间的和大于等于询问的名次则把这个询问分到“向上二分”的集合中,否则分到“向下二分”的集合中。

考虑修改。如果一个修改所加入的数不大于(mid),那么对于已经认定答案在([mid+1,r])的询问一定是没有贡献的,所以只需要加到向下二分的集合中;如果一个修改所加入的数大于(mid),那么对于已经认定答案在([l,mid])的询问一定是有(1)的贡献。如果把答案在([l,mid])的询问所求的名次都减去(1),则这个修改也只会对向上二分的集合中的询问有贡献,只需要加到向上二分的集合中。这样每次都把所有操作分成独立的两部分,最多分(log_2n)次。每层所有操作集合的并集刚好是原集合,所以每层的时间复杂度是(nlog_2n),总复杂度(O(nlog_2^2n))。具体流程可以参考代码。

代码:

并不需要真正把操作分成两个集合,只分它们的编号即可。

#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
using namespace std;

namespace zyt
{
	template<typename T>
	inline void read(T &x)
	{
		char c;
		bool f = false;
		x = 0;
		do
			c = getchar();
		while (c != '-' && !isdigit(c));
		if (c == '-')
			f = true, c = getchar();
		do
			x = x * 10 + c - '0', c = getchar();
		while (isdigit(c));
		if (f)
			x = -x;
	}
	template<typename T>
	inline void write(T x)
	{
		static char buf[20];
		char *pos = buf;
		if (x < 0)
			putchar('-'), x = -x;
		do
			*pos++ = x % 10 + '0';
		while (x /= 10);
		while (pos > buf)
			putchar(*--pos);
	}
	typedef long long ll;
	const int N = 5e4 + 10, B = 16, CHANGE = 1, QUERY = 2;
	int n, m, id[N], ans[N];
	struct node
	{
		int type, l, r;
		ll c;
	}opt[N];
	namespace Segment_Tree
	{
		struct node
		{
			ll sum, tag;
		}tree[1 << (B + 1)];
		inline void update(const int rot)
		{
			tree[rot].sum = tree[rot << 1].sum + tree[rot << 1 | 1].sum;
		}
		inline void push_down(const int rot, const int lt, const int rt)
		{
			if (tree[rot].tag)
			{
				ll &tag = tree[rot].tag;
				int mid = (lt + rt) >> 1;
				tree[rot << 1].sum += tag * (mid - lt + 1);
				tree[rot << 1].tag += tag;
				tree[rot << 1 | 1].sum += tag * (rt - mid);
				tree[rot << 1 | 1].tag += tag;
				tag = 0;
			}
		}
		void add(const int rot, const int lt, const int rt, const int ls, const int rs, const int x)
		{
			if (ls <= lt && rt <= rs)
			{
				tree[rot].sum += x * (rt - lt + 1), tree[rot].tag += x;
				return;
			}
			int mid = (lt + rt) >> 1;
			push_down(rot, lt, rt);
			if (ls <= mid)
				add(rot << 1, lt, mid, ls, rs, x);
			if (rs > mid)
				add(rot << 1 | 1, mid + 1, rt, ls, rs, x);
			update(rot);
		}
		ll query(const int rot, const int lt, const int rt, const int ls, const int rs)
		{
			if (ls <= lt && rt <= rs)
				return tree[rot].sum;
			int mid = (lt + rt) >> 1;
			ll ans = 0;
			push_down(rot, lt, rt);
			if (ls <= mid)
				ans += query(rot << 1, lt, mid, ls, rs);
			if (rs > mid)
				ans += query(rot << 1 | 1, mid + 1, rt, ls, rs);
			return ans;
		}
	}
	void solve(const int idl, const int idr, const int l, const int r)
	{//As for any question, determine
	//wheather there are more than opt[i].c numbers greater than mid or not
	//If so, ans[i] will be greater than mid. Otherwise less.
		using Segment_Tree::query;
		using Segment_Tree::add;
		static int newl[N], newr[N];
		if (l == r)
		{
			for (int i = idl; i <= idr; i++)
				if (opt[id[i]].type == QUERY)
					ans[id[i]] = l;
			return;
		}
		int lcnt = 0, rcnt = 0;
		int mid = (l + r) >> 1;
		for (int i = idl; i <= idr; i++)
		{
			if (opt[id[i]].type == CHANGE)
			{
				if (opt[id[i]].c <= mid)
					newl[lcnt++] = id[i];
				else
				{
					add(1, 1, n, opt[id[i]].l, opt[id[i]].r, 1);
					query(1, 1, n, 100, 100);
					newr[rcnt++] = id[i];
				}
			}
			else
			{
				ll tmp = query(1, 1, n, opt[id[i]].l, opt[id[i]].r);
				if (tmp < opt[id[i]].c)
					newl[lcnt++] = id[i], opt[id[i]].c -= tmp;
				else
					newr[rcnt++] = id[i];
			}
		}
		for (int i = idl; i <= idr; i++)
			if (opt[id[i]].type == CHANGE && opt[id[i]].c > mid)
				add(1, 1, n, opt[id[i]].l, opt[id[i]].r, -1);
		memcpy(id + idl, newl, sizeof(int[lcnt]));
		memcpy(id + idl + lcnt, newr, sizeof(int[rcnt]));
		if (lcnt)
			solve(idl, idl + lcnt - 1, l, mid);
		if (rcnt)
			solve(idl + lcnt, idr, mid + 1, r);
	}
	int work()
	{
		read(n), read(m);
		for (int i = 1; i <= m; i++)
		{
			read(opt[i].type);
			read(opt[i].l);
			read(opt[i].r);
			read(opt[i].c);
			id[i] = i;
		}
		solve(1, m, -N, N);
		for (int i = 1; i <= m; i++)
			if (opt[i].type == QUERY)
				write(ans[i]), putchar('
');
		return 0;
	}
}
int main()
{
	freopen("3110.in", "r", stdin);
	freopen("3110.out", "w", stdout);
	return zyt::work();
}
原文地址:https://www.cnblogs.com/zyt1253679098/p/10012800.html