【Ynoi 2017】由乃打撲克

ProtectEMmm發表於2024-09-02

Luogu P5356 由乃打撲克

題意

給定一個長度為 \(n\) 的序列 \(a\)

\(m\) 次操作:

  1. 查詢區間 \([l,r]\) 中的第 \(k\) 小。
  2. 區間 \([l,r]\) 加上 \(k\)

資料範圍與約定

\(1 \le n,m \le 10^5\)\(-2*10^4 \le k,a_i \le 2*10^4\)

題解

看到區間操作,首先考慮線段樹。

對於操作 1 來說:外層二分答案,線段樹套平衡樹求區間排名可以做。

對於操作 2 來說:發現線段樹不好進行 pushup。

所以把線段樹從 \(logn\) 層變成分塊 \(3\) 層即可。假設塊長為 \(S\),一共有 \(B= \frac{N}{S}\) 塊。

先來說操作 2。發現操作 2 在分塊套平衡樹上還是比較好做的。分兩種情況討論,整塊和角塊。

  • 整塊:塊內打上一個 tag 即可,複雜度 \(O(B)\)
  • 角塊:暴力重建這個塊,複雜度 \(O(SlogS)\)。發現角塊複雜度有點高,發現沒必要每次都重建,之前已經是有序的了,被操作的部分整體也是有序的。所以可以直接合並兩個有序序列,不需要使用平衡樹了。merge 最佳化後複雜度 \(O(S)\),因為我們 merge 的時候同時還要給原陣列加上改變數,所以這裡維護的是原陣列的下標。

算下來總複雜度為 \(O(B+S)\)

再來說操作 1。外層套一個二分,複雜度為 \(logV\)。這裡有一個常數最佳化,先查詢一下區間的最值,然後再在這個最值區間上二分,二分次數會少一些。因為塊內已經有序了,塊外可以暴力,所以代價是複雜度加上了 \(O(B+S)\)。二分的 check 需要塊內查詢。這裡也分兩種情況討論,整塊和角塊。

  • 整塊:因為塊內本身有序,塊內二分即可,這裡我使用了 upper_bound,複雜度 \(O(BlogS)\)
  • 角塊:暴力,複雜度 \(O(S)\)

算下來總複雜度為 \(O(logV*(BlogS+S))\)

發現主要瓶頸在操作 1 上。求一下塊長 \(S\) 取多少時複雜度最低。代入後得到函式式 \(f(S)=\frac{NlogS}{S}+S\) 。發現多了一個 \(logS\) 很不好處理,考慮到 \(S\) 的上界是 \(N\) 後,放縮一下,變成 \(f(S)=\frac{NlogN}{S}+S\),得出 \(S=\sqrt{NlogN}\) 時複雜度最低。

然而,Ynoi 喜歡卡常。所以最後經過不斷測試,發現 \(S=\sqrt{NlogN}/4\approx 300\) 時程式碼跑得最快。當然每個人程式碼實現細節不一樣常數也不一樣,這個地方不一定以我的為準。

程式碼

#include<bits/stdc++.h>
using namespace std;
/*====================*/
#define endl "\n"
/*====================*/
typedef long long lnt;
/*====================*/
const int N = 1e5 + 10;
/*====================*/
const int S = 300;
const int B = N / S + 10;
/*====================*/
const int INF = INT_MAX;
/*====================*/
int n, m, arr[N];
/*====================*/
int belong[N];
/*====================*/
int tree[N];
struct Block
{
	int l, r;
	int delta;
	Block(void)
	{
		l = r = 0; delta = 0;
	}
	void Maintain(int delta)
	{
		this->delta += delta;
	}
	static bool cmp_Maintain(const int& idx1, const int& idx2)
	{
		return arr[idx1] < arr[idx2];
	}
	void Maintain(int l, int r, int delta)
	{
		int idx1 = 0, idx2 = 0;
		static int temp1[N], temp2[N];
		for (int i = this->l; i <= this->r; ++i)
		{
			if (l <= tree[i] && tree[i] <= r)
			{
				arr[tree[i]] += delta;
				temp1[++idx1] = tree[i];
			}
			else
			{
				temp2[++idx2] = tree[i];
			}
		}
		merge(temp1 + 1, temp1 + idx1 + 1, temp2 + 1, temp2 + idx2 + 1, tree + this->l, cmp_Maintain);
	}
};
Block block[B];
/*====================*/
void Change(int l, int r, int delta)
{
	if (belong[l] == belong[r])
	{
		block[belong[l]].Maintain(l, r, delta);
	}
	else
	{
		block[belong[l]].Maintain(l, block[belong[l]].r, delta);
		for (int i = belong[l] + 1; i <= belong[r] - 1; ++i)
		{
			block[i].Maintain(delta);
		}
		block[belong[r]].Maintain(block[belong[r]].l, r, delta);
	}
}
void GetMinMaxVal(int l, int r, int& minval, int& maxval)
{
	if (belong[l] == belong[r])
	{
		for (int i = l; i <= r; ++i)
		{
			minval = min(minval, arr[i] + block[belong[i]].delta);
			maxval = max(maxval, arr[i] + block[belong[i]].delta);
		}
	}
	else
	{
		for (int i = l; i <= block[belong[l]].r; ++i)
		{
			minval = min(minval, arr[i] + block[belong[i]].delta);
			maxval = max(maxval, arr[i] + block[belong[i]].delta);
		}
		for (int i = belong[l] + 1; i <= belong[r] - 1; ++i)
		{
			minval = min(minval, arr[tree[block[i].l]] + block[i].delta);
			maxval = max(maxval, arr[tree[block[i].r]] + block[i].delta);
		}
		for (int i = block[belong[r]].l; i <= r; ++i)
		{
			minval = min(minval, arr[i] + block[belong[i]].delta);
			maxval = max(maxval, arr[i] + block[belong[i]].delta);
		}
	}
}
bool cmp_GetRank(const int& idx1, const int& idx2)
{
	return arr[idx1] + block[belong[idx1]].delta < arr[idx2] + block[belong[idx2]].delta;
}
int GetRank(int l, int r, int val)
{
	int res = 0;
	if (belong[l] == belong[r])
	{
		for (int i = l; i <= r; ++i)
		{
			res += ((arr[i] + block[belong[i]].delta) <= val);
		}
	}
	else
	{
		for (int i = l; i <= block[belong[l]].r; ++i)
		{
			res += ((arr[i] + block[belong[i]].delta) <= val);
		}
		arr[0] = val;
		for (int i = belong[l] + 1; i <= belong[r] - 1; ++i)
		{
			if (arr[tree[block[i].l]] + block[i].delta > val)
			{
				res += 0; continue;
			}
			if (arr[tree[block[i].r]] + block[i].delta <= val)
			{
				res += block[i].r - block[i].l + 1; continue;
			}
			res += upper_bound(tree + block[i].l, tree + block[i].r + 1, 0, cmp_GetRank) - (tree + block[i].l);
		}
		for (int i = block[belong[r]].l; i <= r; ++i)
		{
			res += ((arr[i] + block[belong[i]].delta) <= val);
		}
	}
	return res;
}
/*====================*/
void Solve(void)
{
	do
	{
		cin >> n >> m;
		for (int i = 1; i <= n; ++i)
		{
			cin >> arr[i]; tree[i] = i;
		}
	} while (false);//讀入

	do
	{
		for (int i = 1; i <= n; ++i)
		{
			belong[i] = i / S + 1;
			if (block[belong[i]].l == 0)
			{
				block[belong[i]].l = i;
			}
			block[belong[i]].r = i;
		}
	} while (false);//預處理塊

	do
	{
		for (int i = 1; i <= belong[n]; ++i)
		{
			sort(tree + block[i].l, tree + block[i].r + 1, cmp_GetRank);
		}
	} while (false);//預處理塊內序列

	do
	{
		while (m--)
		{
			int op; cin >> op;
			if (op == 1)
			{
				int l, r, k; cin >> l >> r >> k;
				if (r - l + 1 < k)
				{
					cout << "-1" << endl;
				}
				else
				{
					int ans = -1;
					int minval = +INF, maxval = -INF;
					GetMinMaxVal(l, r, minval, maxval);
					if (k == 1)
					{
						cout << minval << endl; continue;
					}
					if (k == r - l + 1)
					{
						cout << maxval << endl; continue;
					}
					while (minval <= maxval)
					{
						int mid = (1ll * minval + maxval) >> 1;
						if (GetRank(l, r, mid) < k)
						{
							minval = mid + 1;
						}
						else
						{
							ans = mid;
							maxval = mid - 1;
						}
					}
					cout << ans << endl;
				}
			}
			if (op == 2)
			{
				int l, r, delta;
				cin >> l >> r >> delta;
				Change(l, r, delta);
			}
		}
	} while (false);//回答詢問
}
/*====================*/
int main()
{
#ifndef ONLINE_JUDGE
	freopen("IN.txt", "r+", stdin);
#endif
	ios::sync_with_stdio(false);
	cin.tie(NULL), cout.tie(NULL);
	int T = 1; //cin >> T;
	while (T--)Solve();
	return 0;
}

相關文章