1. 程式人生 > 其它 >【題解】P2894 - [USACO08FEB] Hotel G

【題解】P2894 - [USACO08FEB] Hotel G

題目大意

題目連結

給定一個長度為 \(n\) 的序列和 \(m\) 個操作,初始時序列中的 \(n\) 個元素都是空房。每次操作有兩種格式:

  1. 1 x,表示詢問序列中是否存在連續 \(x\) 個空房。若存在,輸出這些空房中最左端空房的編號。如果有多個區間滿足條件,按最左端的區間處理。輸出編號後在選出的 \(x\) 個連續空房內住人。

  2. 2 x y,表示令區間 \([x, x + y - 1]\) 內的客人退房。

\(1 \leq n, m \leq 5 \times 10^4\)

解題思路

首先分析題目中的關鍵詞“連續”。其實對於是否存在 \(x\) 個連續空房的操作而言,我們只需要知道最大連續空房數 \(k\)

並且將 \(k\) 與詢問給出的 \(x\) 相比較,如果 \(k \leq x\) 說明一定存在至少 \(x\) 個連續空房,選擇最左端的 \(x\) 個連續空房即可。因此我們可以發現需要維護這個序列的 最大子段和。我們不妨把空房視作 \(1\),有人住的房間視為 \(-\infty\),這樣直接維護最大子段和得出的就是最大連續空房數。按照這樣的對映關係,第二種操作應該是給整個區間賦值為 \(1\)。聯絡題目需要維護區間、查詢區間,不難想到用 線段樹 來維護。

我們需要維護一個 支援查詢最大子段和給區間統一賦值 的線段樹。查詢最大子段和的方法可以參考 P4513。我們對於每一個區間 \([l, r]\)

都分別維護整個區間的元素之和 \(sum\),在包含左端點的情況下求出的最大子段和 \(lsum\),在包含右端點的情況下求出的最大子段和 \(rsum\),整個區間的最大子段和 \(val\)。顯然整個區間的和等於左半區間的和加右半區間的和,令 \(l\) 為線段樹上當前結點的左子結點,\(r\) 為線段樹上當前結點的右子結點,有 \(sum = sum_{l} + sum_{r}\)

如果必須包含左端點,那麼最大子段和有兩種情況。第一,直接 \(lsum_l\) 轉移,即包含最大子段和的區間被左子區間完全覆蓋。第二,\(lsum_l = sum_l + lsum_r\),即包含最大子段和的區間左子結點為左半區間的左端點,右端點從左半區間延伸到了右半區間。此時左半區間被完全覆蓋,右半區間最好的情況為 \(lsum_r\)

。考慮採用反證法,如果存在一個更好的值 \(k\) 使得 \(k > lsum_r\),那麼此時 \(lsum_r\) 應該等於 \(k\),與 \(lsum_r\) 的定義矛盾,所以從 \(lsum_r\) 轉移一定是最優的。兩者取最大值,即 \(lsum = \max(lsum_l, sum_l + lsum_r)\)。必須包含右端點的最大子段和同理。要麼從 \(rsum_r\) 轉移,要麼覆蓋了整個右半區間並且左端點延伸到了左半區間,此時貢獻為 \(sum_r + rsum_l\)。所以 \(rsum = \max(rsum_r, sum_r + rsum_l)\)

整個區間的最大子段和要麼被左半區間或右半區間直接貢獻,要麼左半區間和右半區間連線後產生了更優的最大子段和。第一種情況的貢獻為 \(\max(val_l, val_r)\),第二種情況只有可能發生在整個區間的中間位置,否則最大子段和不滿足連續。換言之,新產生的最大子段和應該是由左半區間的右邊一部分與右半區間的左邊一部分連線而成的,否則貢獻最大子段和的區間就會斷開。顯然第二種情況的貢獻為 \(rsum_l + lsum_r\),否則類似上面的反證,存在更優值的情況與定義矛盾,所以 \(rsum_l + lsum_r\) 一定是第二種情況的最優貢獻。兩者取較大值就是整個區間的最大子段和,\(val = \max(val_l, val_r, rsum_l + lsum_r)\)

對於區間整體賦值的維護十分簡單,多維護一個 \(lazy\) 標記表示當前區間被賦的值即可,這裡不再贅述。

至此,這道筆者 \(5\) 月份竟然不會做的題就簡單地解決了。是不是非常簡單呢?我也是這樣想的(惱。來,試試看。

參考程式碼

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

const int maxn = 5e4 + 5;

struct node
{
	int l, r;
	long long lsum, rsum;
	long long lazy, sum, val;
} tree[maxn << 2];

int n, m;

void push_up(int k)
{
	tree[k].sum = tree[2 * k].sum + tree[2 * k + 1].sum;
	tree[k].lsum = max(tree[2 * k].lsum, tree[2 * k].sum + tree[2 * k + 1].lsum);
	tree[k].rsum = max(tree[2 * k + 1].rsum, tree[2 * k + 1].sum + tree[2 * k].rsum);
	tree[k].val = max(max(tree[2 * k].val, tree[2 * k + 1].val), tree[2 * k].rsum + tree[2 * k + 1].lsum);
}

void push_down(int k)
{
	if (tree[k].lazy != 0)
	{
		if (tree[k].l == tree[k].r)
		{
			tree[k].lazy = 0;
			return;
		}
		tree[2 * k].sum = (tree[2 * k].r - tree[2 * k].l + 1) * tree[k].lazy;
		tree[2 * k + 1].sum = (tree[2 * k + 1].r - tree[2 * k + 1].l + 1) * tree[k].lazy;
		if (tree[k].lazy > 0)
		{
			tree[2 * k].lsum = tree[2 * k].rsum = tree[2 * k].val = (tree[2 * k].r - tree[2 * k].l + 1) * tree[k].lazy;
			tree[2 * k + 1].lsum = tree[2 * k + 1].rsum = tree[2 * k + 1].val = (tree[2 * k + 1].r - tree[2 * k + 1].l + 1) * tree[k].lazy;
		}
		else
		{
			tree[2 * k].lsum = tree[2 * k].rsum = tree[2 * k].val = tree[k].lazy;
			tree[2 * k + 1].lsum = tree[2 * k + 1].rsum = tree[2 * k + 1].val = tree[k].lazy;
		}
		tree[2 * k].lazy = tree[2 * k + 1].lazy = tree[k].lazy;
		tree[k].lazy = 0;
	}
}

void build(int k, int l, int r)
{
	tree[k].l = l;
	tree[k].r = r;
	if (l == r)
	{
		tree[k].sum = tree[k].lsum = tree[k].rsum = tree[k].val = 1;
		return;
	}
	int mid = (l + r) / 2;
	build(2 * k, l, mid);
	build(2 * k + 1, mid + 1, r);
	push_up(k);
}

void update(int k, int l, int r, long long w) 
{
	if (tree[k].l >= l && tree[k].r <= r)
	{
		tree[k].sum = (tree[k].r - tree[k].l + 1) * w;
		if (w > 0)
			tree[k].lsum = tree[k].rsum = tree[k].val = (tree[k].r - tree[k].l + 1) * w;
		else
			tree[k].lsum = tree[k].rsum = tree[k].val = w;
		tree[k].lazy = w;
		return;
	}
	push_down(k);
	int mid = (tree[k].l + tree[k].r) / 2;
	if (l <= mid)
		update(2 * k, l, r, w);
	if (r > mid)
		update(2 * k + 1, l, r, w);
	push_up(k);
}

node query_sum(int k, int l, int r)
{
	if (tree[k].l >= l && tree[k].r <= r)
		return tree[k];
	int mid = (tree[k].l + tree[k].r) / 2;
	push_down(k);
	if (r <= mid)
		return query_sum(2 * k, l, r);
	if (l > mid)
		return query_sum(2 * k + 1, l, r);
	else
	{
		node ret;
		node lson = query_sum(2 * k, l, mid);
		node rson = query_sum(2 * k + 1, mid + 1, r);
		ret.sum = lson.sum + rson.sum;
		ret.lsum = max(lson.lsum, lson.sum + rson.lsum);
		ret.rsum = max(rson.rsum, rson.sum + lson.rsum);
		ret.val = max(max(lson.val, rson.val), lson.rsum + rson.lsum);
		return ret;
	}
}

int query_idx(int k, long long w)
{
	if (tree[k].l == tree[k].r)
		return tree[k].l;
	push_down(k);
	if (tree[2 * k].val >= w)
		return query_idx(2 * k, w);
	else if (tree[2 * k].rsum + tree[2 * k + 1].lsum >= w)
		return tree[2 * k].r - tree[2 * k].rsum + 1;
	else if (tree[2 * k + 1].val >= w)
		return query_idx(2 * k + 1, w);
}

int main()
{
	int opt, x, y;
	scanf("%d%d", &n, &m);
	build(1, 1, n);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d", &opt);
		if (opt == 1)
		{
			scanf("%d", &x);
			long long val = query_sum(1, 1, n).val;
			if (val >= x)
			{
				int idx = query_idx(1, x);
				update(1, idx, idx + x - 1, -maxn);
				printf("%d\n", idx);
			}
			else
				puts("0");
		}
		else
		{
			scanf("%d%d", &x, &y);
			update(1, x, x + y - 1, 1);
		}
	}
	return 0;
}