[學習-思考-探究]莫隊算法 曼哈頓最小生成樹與分塊區間詢問算法-2
阿新 • • 發佈:2017-08-13
iostream using space style 聯系 const ear math 模版 若要轉載,不需要聯系我,只需要在下面回復一下並註明原文。
在線區間詢問算法(增強算法)
考慮保存狀態
例題:小Z的襪子
如果對小Z的襪子應用基礎算法,會發生什麽?
小Z的襪子這道題目,僅僅知道區間[l, r]的答案是不能更新到[l, r+1]的。
為什麽?因為還要記錄每個區間的狀態,也就是每種顏色在區間內出現的次數。
顯然我們不能直接把狀態保存進每兩個端點構成的區間。
兩端點構成的區間有n個,一個狀態要n個變量,顯然空間是不能承受的。(實際上預處理時間也不能承受..)
(當然你可以用之前所述的優化算法來實現..但是略慢一點,後面會一起講。)
註意到這個狀態滿足區間可減性。
例如1-10中有5個紅色,5個白色
1-5中有2個紅色,3個白色。
那麽6-10中有3個紅色,2個白色。
可以把狀態中每一個變量分別相減,也就是狀態滿足區間可減性。
同理狀態也滿足區間可加性。
那麽我們可以使用前綴和思想,維護序列某一個前綴的狀態。為了節省空間,我們只記錄了序列最左邊的點,到每一個塊端點的狀態。
具體計算過程,請看以下代碼
for (int i = 0; i < n; i++) { insert(tans, tmaxn-2, col[i]); if ((i+1) % block_size == 0) { int b_in = (i+1) / block_size; for (int j = 1; j <= n; j++) { pre[b_in].cnt[j] = st[tmaxn-2].cnt[j]; } } if (i % block_size == 0) { //這個循環的範圍僅針對小Z的襪子! int b_in = i / block_size; for (int j = 1; j <= n; j++) { update(tmaxn-2, j); st[b_in].cnt[j] = st[tmaxn-2].cnt[j]; } clear(tmaxn-1); LL ans = 0; for (int j = i; j < n; j++) { insert(ans, tmaxn-1, col[j]); if (j % block_size == 0) { val[b_in][j/block_size] = ans; } } } }
pre就是這個前綴和。同時我們還把任意兩個塊端點之間的答案存入了val中。
考慮基礎算法中的做法,對於詢問,我們也從端點開始擴展,不過我們可以利用前綴和恢復狀態。顯然我們不能把n個變量一一恢復,會超時。
考慮懶惰思想,我們只記錄相減的兩個前綴的位置,當需要用到某一個變量的時候再更新。
該算法的復雜度是#O(nsqrt{n})#的。如果還沒有看懂,可以看以下代碼。
#include <iostream> #include <algorithm> #include <cmath> #include <vector> #include <cstdio> using namespace std; //該模版需要狀態滿足區間可減性 //該模版寫的是 小Z的襪子 ... typedef long long LL; const int maxn = 55555; const int tmaxn = 230; const int stat_size = maxn; int block_size; struct stat { int cnt[stat_size]; int ts_sub[stat_size]; int tms_sub, suba, subb; } st[tmaxn], pre[tmaxn]; inline void update(int x, int y) { if (st[x].ts_sub[y] < st[x].tms_sub) { st[x].ts_sub[y] = st[x].tms_sub; if (st[x].suba < 0) { st[x].cnt[y] = 0; } else st[x].cnt[y] = st[st[x].suba].cnt[y]-pre[st[x].subb].cnt[y]; } } inline void sub(int x, int a, int b) { st[x].suba = a; st[x].subb = b; st[x].tms_sub ++; } inline void clear(int x) { sub(x, -1, -1); } inline void insert(LL &o, int x, int c) { update(x, c); //這裏是小z的襪子獨有寫法 o -= (((LL)st[x].cnt[c])*(st[x].cnt[c]-1)) >> 1; st[x].cnt[c] ++; o += (((LL)st[x].cnt[c])*(st[x].cnt[c]-1)) >> 1; } LL val[tmaxn][tmaxn]; int col[maxn]; int n, m; vector<int> ra, rb; inline LL getAns(int l, int r) { int tmp_in = tmaxn-1; if (r-l+1 <= block_size) { clear(tmp_in); LL ret = 0; for (int i = l; i <= r; i++) { insert(ret, tmp_in, col[i]); } return ret; } else { int a = l, b = r; ra.clear(), rb.clear(); while (a % block_size) { ra.push_back(a); a++; } while (b % block_size) { rb.push_back(b); b--; } int a_b = a / block_size; int b_b = b / block_size; LL ret = val[a_b][b_b]; sub(tmp_in, b_b, a_b); for (int i = 0; i < ra.size(); i++) { insert(ret, tmp_in, col[ra[i]]); } for (int i = 0; i < rb.size(); i++) { insert(ret, tmp_in, col[rb[i]]); } return ret; } } LL gcd(LL a, LL b) { if (!b) return a; return gcd(b, a%b); } int main() { //freopen("sock.in", "r", stdin); //freopen("sock.out", "w", stdout); scanf("%d%d", &n, &m); for (int i = 0; i < n; i++) { scanf("%d", &col[i]); } block_size = sqrt(n); LL tans = 0; for (int i = 0; i < n; i++) { insert(tans, tmaxn-2, col[i]); if ((i+1) % block_size == 0) { int b_in = (i+1) / block_size; for (int j = 1; j <= n; j++) { pre[b_in].cnt[j] = st[tmaxn-2].cnt[j]; } } if (i % block_size == 0) { //這個循環的範圍僅針對小Z的襪子! int b_in = i / block_size; for (int j = 1; j <= n; j++) { update(tmaxn-2, j); st[b_in].cnt[j] = st[tmaxn-2].cnt[j]; } clear(tmaxn-1); LL ans = 0; for (int j = i; j < n; j++) { insert(ans, tmaxn-1, col[j]); if (j % block_size == 0) { val[b_in][j/block_size] = ans; } } } } for (int i = 1; i <= m; i++) { int l, r; scanf("%d%d", &l, &r); LL ans = getAns(l-1, r-1); LL len = r-l+1; if (ans == 0) { printf("0/1\n"); } else { LL fm = len*(len-1)/2; LL g = gcd(ans, fm); printf("%lld/%lld\n", ans/g, fm/g); } } return 0; }
[學習-思考-探究]莫隊算法 曼哈頓最小生成樹與分塊區間詢問算法-2