1. 程式人生 > 實用技巧 >#10471. 「2020-10-02 提高模擬賽」灌溉 (water)

#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;
}