離線算法
\[225\]賴泓安
自我介紹一下
建中\(225\)
我叫賴泓安
\(Handle:ancuber1031\)或類似的
\(Reference\)
等等這個顏色的是題目連結
Abstract
先看看今天要幹麻
- 莫隊算法
- 整體二分搜
- CDQ分治
- Tarjan求LCA
我覺得code的template好醜
算法概念簡介
什麼是離線?
離線的相反是什麼?
當然就是在線啊
阿在線又是啥?
簡單來講就是秒讀秒回
問題一丟出來
你就馬上回答
比較:
在線:
每當輸入一個詢問,就馬上輸出結果
離線:
一次吃完所有詢問,
經過一些操作後求解,再全部輸出
普通莫隊
應該是分塊的東西
莫隊概念
考慮一個區間詢問的問題,假設\([l,r]\)的答案是\(ans(l,r)\)
如果對於\(ans(l-1,r),ans(l,r+1),ans(l+1,r),ans(l,r-1)\)
可以有效率的從\(ans(l,r)\)轉移
利用分塊的概念排序詢問
然後暴力轉移每筆詢問
莫隊作法
選定每塊大小為\(k\)
詢問左界依照所在的塊排序
同一塊內的詢問以右界大小排序
掃過排序好的詢問
每筆詢問都從上一塊暴力轉移
(一步步移動左右界)
複雜度
轉移複雜度\(O(P)\),塊數為\(S\),共有\(\frac{N}{k}\)塊
對於左界
在同一塊中的尋問每個最多移動\(k\)次,換塊總次數是\(N\)次
左界複雜度\(O(kQ+N)\)
右界則是塊數x\(N\),複雜度\(O(N \frac{N}{k})\)
總複雜度\(O((kQ+N+\frac{N^2}{k})P)\)
取最小:\(O(PN \sqrt{Q})\),此時\(k = \frac{N}{\sqrt{Q}}\)
Code
#pragma GCC optimize("Ofast,unroll-loops,no-stack-protector,fast-math")
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define pii pair<int,int>
#define pll pair<ll,ll>
#define p_q priority_queue
int n, Q, k;
int l = 1, r = 0;
int cur = 0;
vector<int> a;
vector<int> cnt(1e5+5,0), am(1e5+5,0);
struct query{
int ql, qr, id;
bool operator<(query b) {
if (ql/k == b.ql/k) return qr < b.qr;
return ql/k < b.ql/k;
}
};
void sub(int v) {
am[cnt[v]]--;
cnt[v]--;
am[cnt[v]]++;
if (cur==cnt[v]+1 && am[cnt[v]+1]==0) cur = cnt[v];
}
void add(int v) {
am[cnt[v]]--;
cnt[v]++;
am[cnt[v]]++;
cur = max(cur,cnt[v]);
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>Q;
k = n/sqrt(Q);
a.resize(n+1);
vector<query> q(n);
for (int i = 1; i <= n; ++i) cin>>a[i];
for (int i = 0; i < Q; ++i) {
cin>>q[i].ql>>q[i].qr;
q[i].id = i;
}
sort(q.begin(),q.end());
vector<int> ans(Q);
am[0] = n;
for (auto i : q) {
while (r < i.qr) add(a[++r]);
while (r > i.qr) sub(a[r--]);
while (l < i.ql) sub(a[l++]);
while (l > i.ql) add(a[--l]);
ans[i.id] = cur;
}
for (int i = 0; i < Q; ++i) {
cout<<ans[i]<<endl;
}
return 0;
}
\(Hint:\)壓常是個好東西:)
Code
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define p_q priority_queue
#define endl '\n'
#define pb push_back
const int maxn = 2e5;
int n, Q, k, a, cnt = 0;
int arr[maxn+5], ans[maxn+5], num[maxn+5] = {0};
map<int,int> mp;
struct Query{
int l, r, id;
bool operator<(Query b) {
if (l/k == b.l/k) return r < b.r;
return l/k < b.l/k;
}
} q[maxn+5];
inline void add(int p) {
if (num[arr[p]]++ == 0) cnt++;
}
inline void sub(int p) {
if (--num[arr[p]] == 0) cnt--;
}
signed main() {
scanf("%d%d",&n,&Q);
k = n/sqrt(Q);
for (int i = 1; i <= n; ++i) {
scanf("%d",&a);
if (mp.find(a) != mp.end()) arr[i] = mp[a];
else arr[i] = mp[a] = i;
}
for (int i = 1; i <= Q; ++i) {
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id = i;
}
sort(q+1,q+Q+1);
int l = 1, r = 0, ql, qr;
for (int i = 1; i <= Q; ++i) {
ql = q[i].l, qr = q[i].r;
while(l < ql) sub(l++);
while(r > qr) sub(r--);
while(l > ql) add(--l);
while(r < qr) add(++r);
ans[q[i].id] = cnt;
}
for (int i = 1; i <= Q; ++i) printf("%d\n",ans[i]);
return 0;
}
帶修改莫隊
普通莫隊加上時間維度!
說明
我們原本假設的是\([l,r]\)的答案是\(ans(l,r)\)
現在我們加上一個\(time\)的維度,代表經過了幾次修改
\(ans(l,r,time)\)代表區間\([l,r]\)中,經歷過\(time\)次修改的答案
轉移概念跟普通莫隊一樣
怎麼排序呢?
直接看結論
選定每塊大小(結論:\(n^{\frac{2}{3}}\))
第一順位依照左界所在的塊排序
第二順位以右界所在的塊排序
第三順位以\(time\)排序
複雜度
左右端點的塊不變,\(time\)會向右單調,\(O(n)\)
左右端點改變,\(time\)最大移動\(O(n)\)
左右端點可能的塊各是\(n^{\frac{1}{3}}\)
總複雜度:\(n^{\frac{1}{3}} \cdot n^{\frac{1}{3}} \cdot n\)
\(O(n^{\frac{5}{3}})\)
所以很爛:)
跟\(n^2\)沒差多少阿其實
回滾莫隊
差不多是被砍斷一隻手的意思
說明
剛剛的普通莫隊我們可以進行加入和刪除的動作
但假設今天我們只能做其中一個操作時怎麼辦?
拿RMQ問題舉例
我們如果要求多筆詢問區間的最小值
可以很簡單的\(O(1)\)拓展區間
但刪除卻沒有一個有效率的方法
當然我知道RMQ都砸線段樹
回滾莫隊
這時候回滾莫隊可以保證\(O(N \sqrt{Q})\)內求解
且只需要用到加入操作
實作
實作時我們分成三種\(Case\)處理
- \(l,r\)在同一個分塊內
- \(r\)和上一筆詢問位於同一塊中
- \(r\)和上一筆詢問位於不同塊中
\(l,r\)位於同一塊內
反正塊不會太大(\(\frac{N}{\sqrt{Q}}\))
如果所有詢問都長這樣頂多也就\(O(N\sqrt{Q})\)
遇到這種直接暴力掃過區間即可
\(r\)和上一詢問同塊
這時候\(l\)一定遞增,右界作法相同
我們只要把左界拉回下一塊的第一個位置
(對每一塊都維護一個左端點到右界的答案)
然後拓展回到\(r\)即可
\(r\)和上一詢問不同塊
把左界設為下一塊的第一個
右界設為這一塊的最後一個
然後照普通莫隊拓展
Code
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define p_q priority_queue
#define endl '\n'
#define pb push_back
int n, k, Q;
int mn = 1e9+5, cmin = 1e9+5;
int arr[200005];
int ans[200005];
struct Query{
int ql, qr, id;
bool operator<(Query b) {
if (ql/k == b.ql/k) return qr < b.qr;
return ql/k < b.ql/k;
}
} q[200005];
signed main() {
scanf("%d%d",&n,&Q);
k = n/sqrt(Q);
for (int i = 1; i <= n; ++i) cin>>arr[i];
for (int i = 1; i <= Q; ++i) {
scanf("%d%d",&q[i].ql,&q[i].qr);
q[i].id = i;
}
sort(q+1,q+Q+1);
int l = 1, r = 0, pre = -1;
for (int i = 1; i <= Q; ++i) {
int cl = q[i].ql, cr = q[i].qr;
if (cl/k == cr/k) {
mn = 1e9+5;
for (int j = cl; j <= cr; ++j) mn = min(mn,arr[j]);
} else if (cl/k == pre) {
l = (cl/k)*k+k;
while(r < cr) cmin = min(arr[++r],cmin);
mn = cmin;
while(l > cl) mn = min(mn,arr[--l]);
} else {
l = (cl/k)*k+k, r = (cl/k)*k+k-1;
pre = (cl/k);
cmin = mn = 1e9+5;
while(r < cr) cmin = min(arr[++r],cmin);
mn = cmin;
while(l > cl) mn = min(mn,arr[--l]);
}
ans[q[i].id] = mn;
}
for (int i = 1; i <= Q; ++i) printf("%d\n",ans[i]);
return 0;
}
整體二分
如果每筆詢問可以二分搜
但詢問太多,每次都二分搜一次會TLE
可以吃完詢問在一起二分!
一般二分
在給一長度為\(n\)之序列\(arr\),求其中第\(k\)小的數
數字值域為\(1\) ~ \(C\)
除了直接排序之外還有什麼辦法?
對值域區間二分搜,
算有幾個數字小於\(mid\)(設為\(m\))
則對於每次猜測\(mid\)而言
如果\(m_{mid} < k,l = mid\)
如果\(m_{mid} > k,r = mid\)
複雜度:\(O(n log C)\)
現在題目一樣,但我要多次詢問第\(k\)小
如果我每筆詢問都二分搜一次
複雜度:\(O(Qn log C)\)
有點慢
不如我一次二分搜全部
具體怎麼做?
對於當前處理的詢問
都猜它們的答案是\(mid\),然後一樣算\(m_{mid}\)
如果詢問的答案應該等於或更小就丟左邊,否則丟右邊
到\(l == r\)的時候就是當前詢問的答案
對原陣列做一樣的左右丟的操作
最多二分\(log C\)層,\(O(Q)\)把詢問分兩邊
總複雜度:\(O((Q+N)log C)\)
Code
struct Query {int id, k;};
int ans[N];//the answer of the i-th query
int check(int x);
void solve(int l, int r, vector<Query> q) {
if (l == r) {
for (auto i : q) ans[i.id] = l;
return;
}
int mid = (l+r)>>1;
int c = check(mid);
vector<Query> ql, qr;//part
for (auto i : q) {
if (i.k <= c) ql.push_back(i);
else qr.push_back({i.id,i.k-c});//important
}
solve(l,mid,ql), solve(mid+1,r,qr);
return;
}
多次求區間第k小
換個方式維護陣列
但為了能高效率維護前綴,這邊我們需要使用BIT
例如:\([2,7,1,8,2,8,5,7,4]\)
\(Query\):\([l,r,k]\)
1.\([1,4,1]\)
2.\([2,3,2]\)
3.\([1,5,5]\)
\(C = 10\), 當前\(mid = 5\)
\([2,7,1,8,2,8,5,7,4]\)
\(C = 10\), 當前\(mid = 5\)
\(array\):\([1,0,1,0,1,0,1,0,1]\)
\(Pre:[0,1,1,2,2,3,3,4,4,5]\)
1.\([1,4,1]\):\(2-0 > 1\),\(ans<mid\),丟左邊
2.\([2,3,2]\):\(2-1 < 2\),\(ans>mid\),丟右邊
3.\([1,5,5]\):\(5-0 = 5\),\(ans=mid\),丟左邊
多次求區間第k小,帶修改
當前詢問依照時間順序做
遇到修改就當作刪除再加入數字
然後根據當前\(mid\)改\(array\)的值
就會變成維護動態前綴和
複雜度
序列長\(N\),詢問數\(Q\),值域\(C\)
每層需要\(O((Q+N)logN)\)
全部需要\(O((Q+N)logNlogC)\)
例題:
Code
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define pb push_back
#define int long long
struct BIT{
vector<int> a;
int sz = 0;
void init(int _n) {
sz = _n;
a.assign(_n+5,0);
}
void add(int p, int v) {
while(p <= sz) {
a[p] += v;
p += p&-p;
}
}
int sum(int p) {
int ret = 0;
while(p) {
ret += a[p];
p -= p&-p;
}
return ret;
}
} bit;
struct Query{ int cmd, l, r, k, id; };
vector<int> ans(10005,1LL<<31);
vector<int> arr(50005);
void slv(int l, int r, vector<Query> &q) {
if (l == r || q.empty()) {
for (auto[cmd,ql,qr,k,id] : q) if(cmd) ans[id] = l;
return;
}
int mid = (l+r)>>1;
vector<Query> q1, q2;
for (auto[cmd,ql,qr,k,id] : q) {
if (cmd) {
if (k <= bit.sum(qr)-bit.sum(ql-1)) q1.pb({cmd,ql,qr,k,id});
else q2.pb({cmd,ql,qr,k-bit.sum(qr)+bit.sum(ql-1),id});
} else {
if (qr <= mid) {
bit.add(ql,k);
q1.pb({cmd,ql,qr,k,id});
} else {
q2.pb({cmd,ql,qr,k,id});
}
}
}
for (auto[cmd,ql,qr,k,id] : q1) if (!cmd) bit.add(ql,-k);
slv(l,mid,q1), slv(mid+1,r,q2);
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t, n, qq;
cin>>t;
vector<Query> q;
while(t--) {
cin>>n>>qq;
q.clear();
bit.init(n);
for (int i = 1; i <= n; ++i) {
cin>>arr[i];
q.pb({0,i,arr[i],1,0});
}
int cmd, l, r, k;
for (int i = 1; i <= qq; ++i) {
cin>>cmd>>l>>r;
if (cmd == 1) {
cin>>k;
q.pb({1,l,r,k,i});
} else if (cmd == 2) {
q.pb({0,l,arr[l],-1,0});
q.pb({0,l,r,1,0});
arr[l] = r;
} else {
ans[i] = 7122;
}
}
slv(-(1LL<<31),(1LL<<31),q);
for (auto& i : ans) {
if (i < (1LL<<31)) {
cout<<i<<endl;
i = (1LL<<31);
}
}
}
return 0;
}
CDQ分治
一種分治
概念性算法
可以用於解決和點對有關的問題
例如偏序關係
一維偏序
就很顯然,排序完直接算
二維偏序
除了之前教過的類\(merge sort\)的分治算法
(其實這也可以算是一種CDQ分治)
我們還可以對\(x\)排序
然後依序加入點的時候BIT維護\(y\)的前綴和
都是\(O(NlogN)\)
由大到小排序\(x\), 相同時由小到大排序\(y\)
對\(x\)分治
合併區間時改成對\(y\)由大到小排序
用BIT維護\(z\)的前綴和
當然沒人說不行套兩層CDQ分治
實做思路
針對\(x\),假設我們用\(solve(l,r)\)求解\([l,r]\)
且已經用遞迴解決完\([l,mid],[mid+1,r]\)
這時統計對每個在遞迴樹右邊的點
有多少在遞迴樹左邊的點滿足偏序關係
實做思路
對兩邊的點
以\(y\)(由大到小)合併排序
對\(z\)維護前綴和
\(O(Nlog^2N)\)
Code
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define p_q priority_queue
#define endl '\n'
#define pb push_back
const int maxn = 1e5+5;
int n;
vector<int> ans(maxn,0);
struct Point {
int x, y, z, id;
bool operator<(Point b) {
if (x == b.x) return y < b.y;
return x > b.x;
}
};
struct BIT {
vector<int> arr;
int sz;
void init(int _n) {
sz = _n;
arr.assign(sz+5,0);
}
void upd(int p, int v) {
while(p <= sz) {
arr[p] += v;
p += p&-p;
}
}
int sum(int p) {
int ret = 0;
while(p > 0) {
ret += arr[p];
p -= p&-p;
}
return ret;
}
} bit;
void solve(int l, int r, vector<Point> &a) {
if (l == r) return;
int mid = (l+r)>>1;
solve(l,mid,a), solve(mid+1,r,a);
int lp = l, rp = mid+1;
vector<Point> tmp;
while(lp <= mid && rp <= r) {
if (a[lp].y > a[rp].y) {
bit.upd(a[lp].z,1);
tmp.pb(a[lp++]);
} else {
ans[a[rp].id] += (bit.sum(maxn)-bit.sum(a[rp].z));
tmp.pb(a[rp++]);
}
}
while(lp <= mid) {
bit.upd(a[lp].z,1);
tmp.pb(a[lp++]);
}
while(rp <= r) {
ans[a[rp].id] += (bit.sum(maxn)-bit.sum(a[rp].z));
tmp.pb(a[rp++]);
}
for (int i = l; i <= mid; ++i) bit.upd(a[i].z,-1);
for (int i = l; i <= r; ++i) a[i] = tmp[i-l];
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n;
vector<Point> a(n);
bit.init(maxn);
for (int i = 0; i < n; ++i) {
cin>>a[i].x>>a[i].y>>a[i].z;
a[i].id = i;
}
sort(a.begin(),a.end());
solve(0,n-1,a);
for (int i = 0; i < n; ++i) cout<<ans[i]<<endl;
return 0;
}
好我們回到離線
剛剛那個就只是分治
那它跟離線有什麼關係呢?
先看個例題
如果我們把刪除改成加入呢?
問題相當於
每次加入一個數字
問現在有多少個逆序數對
偏序關係
設\(p\)是位置,\(v\)是值
先不管加入的問題,如果\((i,j)\)是逆序對
那一定符合下面的情況
\( p_i < p_j \&\& v_i > v_j \)
\(or\)
\( p_i > p_j \&\& v_i < v_j \)
考慮加入時間
設\(t\)是加入時間
逆序對\((i,j)\)同時要滿足\(t_i \leq t_j\)
\( p_i < p_j \&\& v_i > v_j \)
\( p_i > p_j \&\& v_i < v_j \)
顯然的三維偏序
1. \(t_i \leq t_j\)
2. \( p_i > p_j\)
3. \(v_i < v_j \)
1. \(t_i \leq t_j\)
2. \( p_i < p_j\)
3. \(v_i > v_j \)
Code
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
#define pb push_back
#define int long long
const int maxn = 1e5+1;
int n, m, x;
vector<int> ans(100005,0);
struct node{
int p, v, t = 0;
bool operator<(node b) {
if (t == b.t) return p < b.p;
return t < b.t;
}
};
struct BIT {
vector<int> arr;
int sz;
void init(int _n) {
sz = _n;
arr.assign(sz+5,0);
}
void upd(int p, int v) {
while(p <= sz) {
arr[p] += v;
p += p&-p;
}
}
int sum(int p) {
int ret = 0;
while(p > 0) {
ret += arr[p];
p -= p&-p;
}
return ret;
}
} bit;
//ti >= tj, pi > pj, vi < vj
void solve1(int l, int r, vector<node> &a) {
if (l == r) return;
int mid = (l+r)>>1;
solve1(l,mid,a), solve1(mid+1,r,a);
int lp = l, rp = mid+1;
vector<node> tmp;
while(lp <= mid && rp <= r) {
if (a[lp].p < a[rp].p) {
bit.upd(a[lp].v,1);
tmp.pb(a[lp++]);
} else {
ans[a[rp].t] += bit.sum(maxn)-bit.sum(a[rp].v);
tmp.pb(a[rp++]);
}
}
while(lp <= mid) {
bit.upd(a[lp].v,1);
tmp.pb(a[lp++]);
}
while(rp <= r) {
ans[a[rp].t] += bit.sum(maxn)-bit.sum(a[rp].v);
tmp.pb(a[rp++]);
}
for (int i = l; i <= mid; ++i) bit.upd(a[i].v,-1);
for (int i = l; i <= r; ++i) a[i] = tmp[i-l];
}
//ti >= tj, pi < pj, vi > vj
void solve2(int l, int r, vector<node> &a) {
if (l == r) return;
int mid = (l+r)>>1;
solve2(l,mid,a), solve2(mid+1,r,a);
int lp = l, rp = mid+1;
vector<node> tmp;
while(lp <= mid && rp <= r) {
if (a[lp].p > a[rp].p) {
bit.upd(a[lp].v,1);
tmp.pb(a[lp++]);
} else {
ans[a[rp].t] += bit.sum(a[rp].v-1);
tmp.pb(a[rp++]);
}
}
while(lp <= mid) {
bit.upd(a[lp].v,1);
tmp.pb(a[lp++]);
}
while(rp <= r) {
ans[a[rp].t] += bit.sum(a[rp].v-1);
tmp.pb(a[rp++]);
}
for (int i = l; i <= mid; ++i) bit.upd(a[i].v,-1);
for (int i = l; i <= r; ++i) a[i] = tmp[i-l];
}
signed main() {
scanf("%lld%lld",&n,&m);
vector<node> a(n);
vector<int> pos(n+1);
bit.init(maxn);
for (int i = 0; i < n; ++i) {
scanf("%lld",&a[i].v);
a[i].p = pos[a[i].v] = i;
}
for (int i = m; i > 0; --i) {
scanf("%lld",&x);
a[pos[x]].t = i;
}
sort(a.begin(),a.end());
solve1(0,n-1,a);
sort(a.begin(),a.end());
solve2(0,n-1,a);
for (int i = 1; i <= m; ++i) ans[i] += ans[i-1];
for (int i = m; i > 0; --i) printf("%lld\n",ans[i]);
return 0;
}
記得開long long:(
Tarjan's LCA
對,又是他
以\(DFS\)為主要算法結構
在\(DFS\)的過程中
紀錄訪問順序
輔以\(DSU\)去處理詢問
定義一些我們需要的東西
\(bnd[i]\):存所有跟節點\(i\)有關的詢問
\(prt\)陣列:每個節點的\(boss\),預設是自己
性質
\(DFS\)時,\(u,v\)的\(LCA\)一定比\(u,v\)更晚被\(pop\)掉
換個角度,對於\(u,v\)兩點
在\(DFS\)時,一定是先從\(u\)離開到\(LCA\)
然後從\(LCA\)走到\(v\)
對\(v\)而言,\(LCA\)會是目前時間最近的非初次造訪點
所以我們可以做些什麼呢?
當\(DFS\)完子樹,離開一個節點\(v\)時
我們就把\(prt[v]\)設成\(v\)的父節點
這樣有什麼效果?
對於一組詢問\((u.v)\)
假設\(u\)已經被訪問過
這時候我如果訪問到\(v\)
那\(u\)的當前祖先正好會是\(LCA\)
畫圖解釋
實做
void Tarjan(int u,int f){//f:父節點
for all queries about u://處理和u有關的詢問
//如果相對的詢問點v有訪問過:
LCA(u,v) = find_boss(v)
//這邊套用DSU
for all children of u:
//DFS
prt[u] = f;
}
Code
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define endl '\n'
#define pb push_back
map<pii,int> mp;
vector<int> dep, prt;
int fb(int x) {
if (x == prt[x]) return x;
return prt[x] = fb(prt[x]);
}
void Tarjan(int u,int f,int d,vector<vector<int> > &g,vector<set<int> > &bnd){
dep[u] = d;
for (auto v : bnd[u]) {
if (dep[v]) {
mp[{u,v}] = mp[{v,u}] = fb(v);
}
}
for (auto v : g[u]) {
if (v != f) {
Tarjan(v,u,d+1,g,bnd);
}
}
prt[u] = f;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, Q; cin>>n>>Q;
vector<vector<int> > g(n+1);
for (int i = 1; i < n; ++i) {
int a, b; cin>>a>>b;
g[a].pb(b);
g[b].pb(a);
}
vector<set<int> > bnd(n+1);
dep.assign(n+1,0), prt.assign(n+1,0);
for (int i = 1; i <= n; ++i) prt[i] = i;
vector< pii > q(Q);
for (int i = 0; i < Q; ++i) {
int a, b; cin>>a>>b;
q[i] = {a,b};
bnd[a].insert(b);
bnd[b].insert(a);
}
Tarjan(1,1,1,g,bnd);
for (auto [a,b] : q) {
int lca = mp[{a,b}];
cout<<dep[a]+dep[b]-2*dep[lca]<<endl;
}
return 0;
}
其他有趣的離線題
植物大戰殭屍code
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define p_q priority_queue
#define endl '\n'
#define pb push_back
const int maxn = 300005;
vector<int> arr(10005,0);
vector<int> ans(1000005,0);
struct QUERY{
int l, r, v, id;
bool operator<(QUERY b) {
return r < b.r;
}
};
struct TRIE{
vector<vector<int> > t;
vector<int> nr;
int cnt = 0;
void init() {
t.assign(maxn,vector<int>(2,0));
nr.assign(maxn,0);
}
void upd(int p, int x, int d, int l) {
nr[p] = l;
int c = (x>>d)&1;
if (!t[p][c]) t[p][c] = ++cnt;
if (d >= 0) upd(t[p][c],x,d-1,l);
}
int query(int p, int x, int d, int l) {
int c = ((x>>d)&1)^1;
if (!t[p][c] || nr[t[p][c]] < l) c ^= 1;
if (d == 0) return x^(c<<d);
return (c<<d)^query(t[p][c],x,d-1,l);
}
} trie;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, Q; cin>>n>>Q;
vector<QUERY> q(Q+1);
for (int i = 1; i <= n; ++i) {
cin>>arr[i];
arr[i] ^= arr[i-1];
}
int l, r, v;
for (int i = 1; i <= Q; ++i) {
cin>>l>>r>>v;
q[i] = {l,r,v,i};
}
sort(q.begin()+1,q.end());
int p = 1;
trie.init();
for (int i = 0; i <= n; ++i) {
while(p <= Q && q[p].r == i) {
ans[q[p].id] = trie.query(0,q[p].v^arr[i],30,q[p].l);
p++;
}
trie.upd(0,arr[i],30,i);
}
for (int i = 1; i <= Q; ++i) cout<<ans[i]<<endl;
return 0;
}
Increasing Array Queries
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define p_q priority_queue
#define endl '\n'
#define pb push_back
#define mid ((l+r)>>1)
#define int ll
int n, Q;
struct Query{
int r, id;
};
struct SEG{
vector<int> a, tg;
void init(int _n) {
a.assign(_n<<2,0);
tg.assign(_n<<2,0);
}
void push(int p, int c) {
if (tg[p]) {
a[p] = tg[p]*(c+1);
if (c) tg[p<<1] = tg[p];
if (c) tg[p<<1|1] = tg[p];
tg[p] = 0;
}
}
void upd(int l, int r, int p, int ql, int qr, int v) {
push(p,r-l);
if (ql <= l && r <= qr) {
tg[p] = v;
return;
}
if (qr <= mid) upd(l,mid,p<<1,ql,qr,v);
else if (ql > mid) upd(mid+1,r,p<<1|1,ql,qr,v);
else {
upd(l,mid,p<<1,ql,qr,v);
upd(mid+1,r,p<<1|1,ql,qr,v);
}
a[p] = (tg[p<<1]?(tg[p<<1]*(mid-l+1)):a[p<<1]) + (tg[p<<1|1]?(tg[p<<1|1]*(r-mid)):a[p<<1|1]);
}
int sum(int l, int r, int p, int ql, int qr) {
push(p,r-l);
if (ql <= l && r <= qr) return a[p];
if (qr <= mid) return sum(l,mid,p<<1,ql,qr);
else if (ql > mid) return sum(mid+1,r,p<<1|1,ql,qr);
else return sum(l,mid,p<<1,ql,qr)+sum(mid+1,r,p<<1|1,ql,qr);
}
} seg;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>Q;
seg.init(n);
vector<int> v(n+1,0);
for (int i = 1; i <= n; ++i) {
cin>>v[i];
v[i] += v[i-1];
}
vector<vector<Query> > q(n+1);
vector<int> ans(Q+1);
for (int i = 1; i <= Q; ++i) {
int l, r; cin>>l>>r;
q[l].pb({r,i});
}
for (int i = n; i >= 1; --i) {
int l = i, r = n+1;
while(r-l > 1) {
if (seg.sum(1,n,1,mid,mid) > v[i]-v[i-1]) r = mid;
else l = mid;
}
seg.upd(1,n,1,i,l,v[i]-v[i-1]);
for (auto [R,id] : q[i]) {
ans[id] = seg.sum(1,n,1,i,R)-(v[R]-v[i-1]);
}
}
for (int i = 1; i <= Q; ++i) {
cout<<ans[i]<<endl;
}
return 0;
}
另一題有空補XD
離線
By Lai Hong An
離線
- 270