T2
洛谷 P2839 Middle
對於中位數,考慮二分,將資料中大於等於該數的標為 \(1\),小於的標為 \(0\),求和大於(等於)\(0\)則合法,否則非法。對於這個題,每次詢問可以發現 \([b, c]\) 必選,前後兩端不必全選。因此考慮前後兩端的最大後 / 字首和。接下來考慮如何快速標記。注意到當二分的數每次增加 \(1\) 時,只有一種數的權值會變化。於是對值域建主席樹,每次查詢在對應的樹上查即可。
程式碼
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
int n, q;
int a[200005];
int d[200005], dcnt;
int rt[200005];
pair<int, int> p[200005];
struct node {
int l, r, s, lmx, rmx;
} T[10000005];
node operator+(node a, node b) {
node ret;
ret.s = a.s + b.s;
ret.lmx = max(a.lmx, a.s + b.lmx);
ret.rmx = max(b.rmx, b.s + a.rmx);
ret.l = ret.r = 0;
return ret;
}
struct Segment_Tree {
int ncnt;
void pushup(int o) {
T[o].s = T[T[o].l].s + T[T[o].r].s;
T[o].lmx = max(T[T[o].l].lmx, T[T[o].l].s + T[T[o].r].lmx);
T[o].rmx = max(T[T[o].r].rmx, T[T[o].r].s + T[T[o].l].rmx);
}
void Build(int& o, int l, int r) {
o = ++ncnt;
if (l == r) {
T[o].s = T[o].lmx = T[o].rmx = -1;
return;
}
int mid = (l + r) >> 1;
Build(T[o].l, l, mid);
Build(T[o].r, mid + 1, r);
pushup(o);
// cout << o << " " << l << " " << r << " " << T[o].s << " " << T[o].lmx << " " << T[o].rmx << "\n";
}
void Insert(int& p, int q, int l, int r, int x) {
p = ++ncnt;
T[p] = T[q];
if (l == r) {
T[p].s = T[p].lmx = T[p].rmx = 1;
return;
}
int mid = (l + r) >> 1;
if (x <= mid)
Insert(T[p].l, T[q].l, l, mid, x);
else
Insert(T[p].r, T[q].r, mid + 1, r, x);
pushup(p);
}
node Query(int o, int l, int r, int L, int R) {
if (L > R)
return (node) { 0, 0, 0, 0, 0 };
if (L <= l && r <= R)
return T[o];
int mid = (l + r) >> 1;
if (R <= mid)
return Query(T[o].l, l, mid, L, R);
if (L > mid)
return Query(T[o].r, mid + 1, r, L, R);
return Query(T[o].l, l, mid, L, R) + Query(T[o].r, mid + 1, r, L, R);
}
} seg;
int vrt[200005];
map<int, int> mp;
bool chk(int k, int a, int b, int c, int d) {
// cout << k << " " << vrt[k] << "\n";
int s = seg.Query(vrt[k], 1, n, b + 1, c - 1).s;
int l = seg.Query(vrt[k], 1, n, a, b).rmx;
int r = seg.Query(vrt[k], 1, n, c, d).lmx;
// cout << s << " " << l << " " << r << "\n";
return s + l + r >= 0;
}
int _mp[200005];
int pp;
int Answer(int a, int b, int c, int d) {
// cout << a << " " << b << " " << c << " " << d << "\n";
if (b == c) {
if (a == b) {
if (c == d)
return _mp[::a[a]];
else
return max(_mp[::a[a]], Answer(a, a, c + 1, d));
} else
return max(Answer(a, b - 1, c, d), Answer(b, b, c, d));
}
int l = 1, r = dcnt, mid, ans = -1;
while (l <= r) {
mid = (l + r) >> 1;
if (chk(mid, a, b, c, d))
ans = mid, l = mid + 1;
else
r = mid - 1;
}
return _mp[ans];
}
int main() {
freopen("middle.in", "r", stdin);
freopen("middle.out", "w", stdout);
cin >> n >> pp;
for (int i = 1; i <= n; i++) cin >> a[i], d[i] = a[i];
sort(d + 1, d + n + 1);
d[0] = -1;
for (int i = 1; i <= n; i++) (d[i] != d[i - 1]) ? (_mp[mp[d[i]] = mp[d[i - 1]] + 1] = d[i]) : 0;
dcnt = mp[d[n]];
for (int i = 1; i <= n; i++) p[i] = make_pair(a[i] = mp[a[i]], i);
// for (int i = 1; i <= n; i++) cout << a[i] << " ";
// cout << "\n";
sort(p + 1, p + n + 1);
seg.Build(rt[n + 1], 1, n);
for (int i = n; i; i--) {
seg.Insert(rt[i], rt[i + 1], 1, n, p[i].second);
// cout << p[i].second << " " << rt[i] << " " << T[rt[i]].s << " " << T[rt[i]].lmx << " " << T[rt[i]].rmx << "\n";
if (p[i].first != p[i - 1].first)
vrt[p[i].first] = rt[i];
}
cin >> q;
int lans = 0;
while (q--) {
int tmp[4];
cin >> tmp[0] >> tmp[1] >> tmp[2] >> tmp[3];
tmp[0] = (tmp[0] + pp * lans) % n + 1;
tmp[1] = (tmp[1] + pp * lans) % n + 1;
tmp[2] = (tmp[2] + pp * lans) % n + 1;
tmp[3] = (tmp[3] + pp * lans) % n + 1;
sort(tmp, tmp + 4);
cout << (lans = Answer(tmp[0], tmp[1], tmp[2], tmp[3])) << "\n";
}
return 0;
}
T6
洛谷 P2824 排序
考慮二分答案,把所有大於等於當前數的都變成 \(1\),小於的變成 \(0\),然後就變成了 \(01\) 序列區間排序。只需要每次統計區間 \(1\) 的個數然後區間推平即可。由於 \(x\) 單增時 \([x \ge a_p]\) 這個東西是單不降的,所以可以二分。
程式碼
#include <iostream>
using namespace std;
int n, m;
int p[1000005], l[1000005], r[1000005], t[1000005];
struct Segment_Tree {
int s[4000005], tg[4000005];
void Build(int o, int l, int r) {
s[o] = 0, tg[o] = -1;
if (l == r)
return;
int mid = (l + r) >> 1;
Build(o << 1, l, mid);
Build(o << 1 | 1, mid + 1, r);
}
void tag(int o, int l, int r, int t) {
s[o] = t * (r - l + 1);
tg[o] = t;
}
void pushdown(int o, int l, int r) {
if (tg[o] == -1)
return;
int mid = (l + r) >> 1;
tag(o << 1, l, mid, tg[o]);
tag(o << 1 | 1, mid + 1, r, tg[o]);
tg[o] = -1;
}
void Cover(int o, int l, int r, int L, int R, int v) {
if (L > R)
return;
if (L <= l && r <= R) {
tag(o, l, r, v);
return;
}
pushdown(o, l, r);
int mid = (l + r) >> 1;
if (L <= mid)
Cover(o << 1, l, mid, L, R, v);
if (R > mid)
Cover(o << 1 | 1, mid + 1, r, L, R, v);
s[o] = s[o << 1] + s[o << 1 | 1];
}
int Query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R)
return s[o];
pushdown(o, l, r);
int mid = (l + r) >> 1, ret = 0;
if (L <= mid)
ret += Query(o << 1, l, mid, L, R);
if (R > mid)
ret += Query(o << 1 | 1, mid + 1, r, L, R);
return ret;
}
} seg;
int q;
bool chk(int k) {
seg.Build(1, 1, n);
for (int i = 1; i <= n; i++) seg.Cover(1, 1, n, i, i, p[i] >= k);
for (int i = 1; i <= m; i++) {
int s = seg.Query(1, 1, n, l[i], r[i]);
if (t[i]) {
seg.Cover(1, 1, n, l[i], l[i] + s - 1, 1);
seg.Cover(1, 1, n, l[i] + s, r[i], 0);
} else {
seg.Cover(1, 1, n, l[i], r[i] - s, 0);
seg.Cover(1, 1, n, r[i] - s + 1, r[i], 1);
}
}
return seg.Query(1, 1, n, q, q);
}
int main() {
int tc;
cin >> tc;
while (tc--) {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> p[i];
for (int i = 1; i <= m; i++) cin >> t[i] >> l[i] >> r[i];
cin >> q;
int l = 1, r = n, mid, ans = -1;
while (l <= r) {
mid = (l + r) >> 1;
if (chk(mid))
ans = mid, l = mid + 1;
else
r = mid - 1;
}
cout << ans << "\n";
}
return 0;
}
T7
CF1523G Try Booking
先不考慮長度限制,我們考慮類似分治的東西,設 \(calc(l, r)\) 表示 \([l, r]\) 中會租出多少,則每次只需要找編號最小的一次嘗試使得該嘗試被 \([l, r]\) 完全包含,然後由於左右是獨立的,可以直接分治下去。然後考慮倒序列舉區間長度,相當於每次加入一個區間,要支援二維最小值查詢。直接上樹套樹維護即可。對每個長度都做一遍,由於每次做的最多次數是 \(\frac{n}{x}\),總次數是調和級數的,再乘上樹套樹的兩個 \(\log\),可以透過。
程式碼
#include <iostream>
#include <algorithm>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
const int inf = 1000000000;
int n, m;
struct P {
int l, r, id;
} a[100005];
int ans[100005];
struct node {
int l, r, mn;
} T[10000005];
struct Segment_Tree {
int rt, ncnt;
void Add(int& o, int l, int r, int x, int y) {
if (!o) {
// cout << l << " " << r << " " << x << " new\n";
T[o = ++ncnt].mn = inf;
}
if (l == r) {
// cout << l << " " << r << " " << y << " l r y\n";
T[o].mn = min(T[o].mn, y);
return;
}
int mid = (l + r) >> 1;
if (x <= mid)
Add(T[o].l, l, mid, x, y);
else
Add(T[o].r, mid + 1, r, x, y);
T[o].mn = min(T[T[o].l].mn, T[T[o].r].mn);
}
int Query(int o, int l, int r, int L, int R) {
if (!o)
return inf;
if (L <= l && r <= R) {
// cout << l << " " << r << " " << T[o].mn << "\n";
return T[o].mn;
}
int mid = (l + r) >> 1;
int ret = inf;
if (L <= mid)
ret = min(ret, Query(T[o].l, l, mid, L, R));
if (R > mid)
ret = min(ret, Query(T[o].r, mid + 1, r, L, R));
// cout << l << " " << r << " " << ret << " ret\n";
return ret;
}
} seg;
struct BIT {
int bit[100005];
void add(P x) { for (; x.r <= 100000; x.r += lowbit(x.r)) seg.Add(bit[x.r], 1, 100000, x.l, x.id); }
int query(int l, int r) {
int ret = inf;
// cout << l << " " << r << " query ";
for (; r; r -= lowbit(r)) ret = min(ret, seg.Query(bit[r], 1, 100000, l, 100000));
// cout << ret << "\n";
return ret;
}
} bit;
int p[100005];
int calc(int l, int r, int x) {
if (l > r)
return 0;
int cur = bit.query(l, r);
if (cur >= inf)
return 0;
// cout << l << " " << r <<"\n";
// cout << cur << " cur\n";
cur = p[cur];
// cout << cur << " cur\n";
return calc(l, a[cur].l - 1, x) + a[cur].r - a[cur].l + 1 + calc(a[cur].r + 1, r, x);
}
int main() {
T[0].mn = inf;
cin >> n >> m;
for (int i = 1; i <= m; i++) cin >> a[i].l >> a[i].r, a[i].id = i;
sort(a + 1, a + m + 1, [](P a, P b) { return a.r - a.l + 1 > b.r - b.l + 1; });
for (int i = 1; i <= m; i++) p[a[i].id] = i;
for (int i = n, j = 1; i; i--) {
// cout << i <<"\n";
while (j <= m && a[j].r - a[j].l + 1 >= i) bit.add(a[j]), ++j;
ans[i] = calc(1, n, i);
// if (i == n - 1)
// return 0;
}
for (int i = 1; i <= n; i++) cout << ans[i] << "\n";
return 0;
}
T8
Partially Free Meal
按 \(b\) 排序,列舉選的最大的 \(b_i\),相當於一個查詢前 \(k\) 大和的東西。注意到當個數增加時,最優的 \(b_i\) 具有單調性,於是直接分治,需要支援任意字首中前 \(k\) 大和,主席樹維護即可。
程式碼
#include <iostream>
#include <algorithm>
#include <map>
#define int long long
using namespace std;
int n;
pair<int, int> a[200005];
int d[200005], dcnt;
int _mp[200005];
int rt[200005];
struct node {
int l, r, s, S;
} T[10000005];
struct Persistent_Segment_Tree {
int ncnt;
void Insert(int& p, int q, int l, int r, int x) {
p = ++ncnt;
T[p] = T[q];
T[p].s++;
T[p].S += _mp[x];
if (l == r)
return;
int mid = (l + r) >> 1;
if (x <= mid)
Insert(T[p].l, T[q].l, l, mid, x);
else
Insert(T[p].r, T[q].r, mid + 1, r, x);
}
int Query(int p, int l, int r, int k) {
if (l == r)
return _mp[l] * k;
int mid = (l + r) >> 1;
if (T[T[p].l].s >= k)
return Query(T[p].l, l, mid, k);
else
return T[T[p].l].S + Query(T[p].r, mid + 1, r, k - T[T[p].l].s);
}
} seg;
int ans[200005];
void Solve(int l, int r, int ql, int qr) {
if (l > r)
return;
// cout << l << " " << r << "\n";
int mid = (l + r) >> 1;
int k = ql;
ans[mid] = 3000000000000000;
for (int i = max(ql, mid); i <= qr; i++) {
int tmp = seg.Query(rt[i - 1], 1, dcnt, mid - 1) + _mp[a[i].first] + a[i].second;
// cout << i - 1 << " " << tmp << " " << mid - 1 << " s\n";
// int tmp = 0;
if (tmp < ans[mid])
ans[mid] = tmp, k = i;
}
// cout << mid << " " << k << " k\n";
Solve(l, mid - 1, ql, k);
Solve(mid + 1, r, k, qr);
}
map<int, int> mp;
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].second >> a[i].first, d[i] = a[i].second;
sort(d + 1, d + n + 1);
for (int i = 1; i <= n; i++) d[i] != d[i - 1] ? (_mp[mp[d[i]] = mp[d[i - 1]] + 1] = d[i]) : 0;
dcnt = mp[d[n]];
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++) {
swap(a[i].first, a[i].second);
// cout << a[i].first << " " << a[i].second << "\n";
a[i].first = mp[a[i].first];
seg.Insert(rt[i], rt[i - 1], 1, dcnt, a[i].first);
}
Solve(1, n, 1, n);
for (int i = 1; i <= n; i++) cout << ans[i] << "\n";
return 0;
}