#10471. 「2020-10-02 提高模拟赛」灌溉 (water)

题面:#10471. 「2020-10-02 提高模拟赛」灌溉 (water)

假设只有一组询问,我们可以用二分求解:二分最大距离是多少,然后找到深度最大的结点,并且把它的\(k\)倍祖先的一整子树删掉,看一下一共要删几次,显然满足单调性。

现在要询问所有取值。上面二分的过程启发我们可以反过来,通过枚举答案,然后找到答案对应哪些询问。显然对于当前\(\text{ans}\),一次删除最少删掉\(ans+1\)个点,最多删\(\frac{n}{ans+1}\)次,因此是一个调和级数\(\frac{1}{1}+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+....+\frac{1}{n} -> n\ln(n)\),只要保证每次枚举的时间复杂度与\(n\)脱钩就行,用线段树维护剩余数的最大值的位置就行了,时间复杂度\(n\log^2(n)\)

这个线段树的实现比较巧妙,\(\text{lazytag}\)有三种取值,\(0\)表示没有操作,\(1\)表示要把孩子们全删了,\(2\)表示要把孩子们全弄回来。若一个点的懒标记大于零,就直接用它的懒标记覆盖它儿子们的懒标记。为了实现恢复操作,要额外存下每个点最初始的值用作恢复用。


int tree[maxn * 4], val[maxn * 4], fir[maxn * 4];

/// val: 0 -> 无要求; 1 -> 要求填满; 2 -> 要求删掉

int pushup(int x, int y)
{
	return dep[x] < dep[y] ? y : x;
}

void spread(int x)
{
	if (val[x] == 1)
	{
		val[x << 1] = val[x << 1 | 1] = 1;
		tree[x << 1] = fir[x << 1];
		tree[x << 1 | 1] = fir[x << 1 | 1];
	}
	if (val[x] == 2)
	{
		val[x << 1] = val[x << 1 | 1] = 2;
		tree[x << 1] = tree[x << 1 | 1] = 0;
	}
	val[x] = 0;
}

void build(int x, int l, int r)
{
	val[x] = true;
	if (l == r)
	{
		fir[x] = tree[x] = pnt[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(x << 1, l, mid);
	build(x << 1 | 1, mid + 1, r);
	fir[x] = tree[x] = pushup(tree[x << 1], tree[x << 1 | 1]);
}

void erase(int x, int l, int r, int ll, int rr)
{
	//tree[x] *= val[x];
	//tree[x] = fir[x] * val[x];
	if (ll <= l && r <= rr)
	{
		tree[x] = 0; val[x] = 2;
		return;
	}
	spread(x);
	int mid = (l + r) >> 1;
	if (ll <= mid)
	{
		erase(x << 1, l, mid, ll, rr);
	}
	if (rr > mid)
	{
		erase(x << 1 | 1, mid + 1, r, ll, rr);
	}
	tree[x] = pushup(tree[x << 1], tree[x << 1 | 1]);
}

int calc(int k)
{
	//insert(1, 1, 1, 1, n);
	val[1] = 1;
	tree[1] = fir[1];
	int ret = 0;
	while (tree[1])
	{
		int del = Kfat(tree[1], k);
		erase(1, 1, n, dfn[del], dfn[del] + sze[del] - 1);
		ret++;
	}
	return ret;
}

posted @ 2020-10-25 13:51  Linshey  阅读(65)  评论(0编辑  收藏  举报