Luogu P5356 由乃打撲克
題意
給定一個長度為 \(n\) 的序列 \(a\)。
有 \(m\) 次操作:
- 查詢區間 \([l,r]\) 中的第 \(k\) 小。
- 區間 \([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;
}