【无码专区13】最小公倍数(线段树)
因為只有std,沒有自我實現(xiàn),所以是無碼專區(qū)
主要是為了訓(xùn)練思維能力
my idea顧名思義,記錄了我的整個思維過程,以及自己部分實現(xiàn)細(xì)節(jié)口胡,還有期望分?jǐn)?shù)
solution才是dls正解,但是因為只有潦草幾句,所以大部分會有我自己基于正解上面的算法實現(xiàn)過程,可能選擇的算法跟std中dls的實現(xiàn)不太一樣。
std可能也會帶有博主自己的注釋。
problem
有 nnn 個數(shù),其表示為 2ai?3bi2^{a_i}·3^{b_i}2ai??3bi?,給定 ai,bia_i,b_iai?,bi?。
對于所有的非空子集,求出它們的最小公倍數(shù),并求和。
答案對 1e9+71e9+71e9+7 取模。
| 1?21-21?2 | 202020 | 10910^9109 |
| 3?43-43?4 | 10310^3103 | 10910^9109 |
| 5?65-65?6 | 10510^5105 | 10310^3103 |
| 7?107-107?10 | 10510^5105 | 10910^9109 |
my idea
讀完題就直接思考具有特殊性質(zhì)的數(shù)據(jù)點,因為往往這種數(shù)據(jù)點的算法就是正解最樸素的原始樣子。有價值的題都會這么設(shè)計部分分。某些毒瘤題呃呃呃
n≤103n\le 10^3n≤103 應(yīng)該是與 n2n^2n2 掛鉤的算法。
大概是枚舉子集的最小值和最大值,強制入選,然后二維數(shù)點中間所有可以選擇的數(shù)。為了避免算重,相同值的點還得強制規(guī)定一個大小,比如編號。
ai,bi≤103a_i,b_i\le 10^3ai?,bi?≤103 本質(zhì)上與上面差不多。
只不過是從因子的冪次角度入手枚舉。
枚舉最大值的 a,ba,ba,b,二維數(shù)點所有 ai≤a,bi≤ba_i\le a,b_i\le bai?≤a,bi?≤b 的點,考慮是否選擇。
同樣為了避免算重,也得規(guī)定第三排序法則。
以上只是粗略的想法,并未細(xì)想。因為這個時候發(fā)現(xiàn)上面的做法其實是可以做正解的。
2a,3b2^a,3^b2a,3b 是相互獨立的,可分開計算。
將所有數(shù)按 aaa 冪次大小升序排序后。
考慮枚舉第 iii 個數(shù)的 aaa 作為最小公倍數(shù)的 aaa。
顯然,子集內(nèi)的其余元素只能從 1≤j<i1\le j<i1≤j<i 里面選。
對 bbb 建立權(quán)值線段樹,維護(hù)元素個數(shù)。
為了去重,強制枚舉的數(shù)必選,那么最小公倍數(shù) bbb 的至少是 bib_ibi?。
直接線段樹上查一段區(qū)間作為作為最小公倍數(shù) bbb 的答案。
具體而言:
線段樹的葉子節(jié)點 xxx 維護(hù)的是 bj≤x,1≤j<ib_j\le x,1\le j<ibj?≤x,1≤j<i 的 jjj 的個數(shù),假設(shè)為 ccc 個。
一個葉子節(jié)點如果是最后選的子集數(shù)的最小公倍數(shù)的 bbb 的話。
那么可能的子集為 2c?12^{c-1}2c?1 個,?1-1?1 是因為必須要求 bj=xb_j=xbj?=x 的 jjj 中強制被選一個,這樣才會有 bjb_jbj?,否則會假掉。
一個點對答案的貢獻(xiàn)就是 2c?1?x2^{c-1}*x2c?1?x,此時必須保證有至少一個 bj=xb_j=xbj?=x 才行。
所以一個點還要維護(hù)一個標(biāo)記 fff,表示是否有至少一個 jjj 滿足 bj=xb_j=xbj?=x。
那么一個點對答案的貢獻(xiàn)應(yīng)該是 2c?1?x?f2^{c-1}*x*f2c?1?x?f。
對于枚舉的 iii 而言,算出來的貢獻(xiàn)為 2ai?2^{a_i}*2ai?? 線段樹查詢區(qū)間 [bj,MaxB][b_j,MaxB][bj?,MaxB]。
注意到,這個形式意味著還要對 bbb 進(jìn)行離散化處理。
因為 iii 能讓 bib_ibi? 的線段樹對應(yīng)節(jié)點 f=1f=1f=1,而這之前可能是 f=0f=0f=0。
所以要先修改再查詢。
修改根據(jù)節(jié)點維護(hù)信息,對應(yīng)的應(yīng)是區(qū)間修改 [1,bi][1,b_i][1,bi?],且特殊的, bib_ibi? 的 fff 要置為 111。
所以可以拆成區(qū)間修改 [1,bi)[1,b_i)[1,bi?) 和單點修改 bib_ibi?。
懶標(biāo)記一旦增加,相當(dāng)于是多了一個個數(shù),冪次 +1+1+1,拆出來變成外部 ×2\times 2×2。
即 2lazy?(2c1?x1?f1+2c2?x2?f2+...+)2^{lazy}·(2^{c_1}*x_1*f_1+2^{c_2}*x_2*f_2+...+)2lazy?(2c1??x1??f1?+2c2??x2??f2?+...+)。
時間復(fù)雜度為 O(nlog?n)O(n\log n)O(nlogn)。
solution
首先不妨設(shè) ai,bia_i,b_iai?,bi? 兩兩不同。
考慮枚舉集合中 bib_ibi? 最大的元素,將所有 bj<bib_j<b_ibj?<bi? 的元素按 aja_jaj? 從小到大排序。
不妨記作 a1′,a2′,...,ai?1′a_1',a_2',...,a_{i-1}'a1′?,a2′?,...,ai?1′?,那么會有 2i?12^{i-1}2i?1 個子集,最大值是 ai?1′a_{i-1}'ai?1′?。
考慮將 aia_iai? 加入后,所有最大值 <ai<a_i<ai? 的子集,最大值都變成了 aia_iai?,剩下的子集不變。
將 aia_iai? 加入到上面的有序序列后,所有比 aia_iai? 大的元素排名都會 +1+1+1,對應(yīng)的子集個數(shù)會翻倍。
問題等價于區(qū)間乘 222 以及區(qū)間求和,線段樹維護(hù)。
就是my idea類似的思想,yeah我做出來了!
std
#include <bits/stdc++.h> #define ls(x) (x << 1) #define rs(x) ((x << 1) | 1) #define LL long long using namespace std; const int maxn = 100010; const LL mod = 1000000007;struct node {int x, y, rank; };bool cmp1(node x, node y) {return x.x == y.x ? x.y < y.y : x.x < y.x; }bool cmp2(node x, node y) {return x.y == y.y ? x.x < y.x : x.y < y.y; } node a[maxn];struct SegementTree {LL sum, cnt, lz; }; SegementTree tr[maxn * 4];LL qpow(LL x, LL y) {LL ans = 1;for (; y; y >>= 1) {if (y & 1)ans = (ans * x) % mod;x = (x * x) % mod;}return ans; }void pushup(int x) {tr[x].sum = (tr[ls(x)].sum + tr[rs(x)].sum) % mod;tr[x].cnt = (tr[ls(x)].cnt + tr[rs(x)].cnt) % mod; }void maintain(int x, int y) {tr[x].sum = (tr[x].sum * qpow(2, y)) % mod;tr[x].lz += y; }void pushdown(int x) {if (tr[x].lz) {if (tr[ls(x)].cnt)maintain(ls(x), tr[x].lz);if (tr[rs(x)].cnt)maintain(rs(x), tr[x].lz);tr[x].lz = 0;} }void build(int x, int l, int r) {if (l == r) {tr[x].sum = tr[x].cnt = 0;return;}int mid = (l + r) >> 1;build(ls(x), l, mid);build(rs(x), mid + 1, r);pushup(x); }void update_cnt(int x, int l, int r, int pos, int y, int z) {if (l == r) {tr[x].cnt = 1;tr[x].sum = (qpow(2, y) * qpow(2, z)) % mod;return;}pushdown(x);int mid = (l + r) >> 1;if (pos <= mid)update_cnt(ls(x), l, mid, pos, y, z);elseupdate_cnt(rs(x), mid + 1, r, pos, y, z);pushup(x); }void update_sum(int x, int l, int r, int ql, int qr) {if (l >= ql && r <= qr) {tr[x].lz++;tr[x].sum = (tr[x].sum * 2) % mod;return;}pushdown(x);int mid = (l + r) >> 1;if (ql <= mid)update_sum(ls(x), l, mid, ql, qr);if (qr > mid)update_sum(rs(x), mid + 1, r, ql, qr);pushup(x); }LL query_cnt(int x, int l, int r, int ql, int qr) {if (l >= ql && r <= qr) {return tr[x].cnt;}int mid = (l + r) >> 1;pushdown(x);LL ans = 0;if (ql <= mid)ans += query_cnt(ls(x), l, mid, ql, qr);if (qr > mid)ans += query_cnt(rs(x), mid + 1, r, ql, qr);return ans; }LL query_sum(int x, int l, int r, int ql, int qr) {if (l >= ql && r <= qr) {return tr[x].sum;}int mid = (l + r) >> 1;LL ans = 0;pushdown(x);if (ql <= mid)ans += query_sum(ls(x), l, mid, ql, qr);if (qr > mid)ans += query_sum(rs(x), mid + 1, r, ql, qr);return ans % mod; }int main() {freopen("lcm.in", "r", stdin);freopen("lcm.out", "w", stdout);int n;while (~scanf("%d", &n)) {for (int i = 1; i <= n; i++) {scanf("%d%d", &a[i].x, &a[i].y);}sort(a + 1, a + 1 + n, cmp1);for (int i = 1; i <= n; i++) {a[i].rank = i;}sort(a + 1, a + 1 + n, cmp2);build(1, 1, n);LL ans = 0;for (int i = 1; i <= n; i++) {LL tmp = query_cnt(1, 1, n, 1, a[i].rank);update_cnt(1, 1, n, a[i].rank, tmp, a[i].x);ans = (ans + query_sum(1, 1, n, a[i].rank, n) * qpow(3, a[i].y) % mod) % mod;if (a[i].rank != n)update_sum(1, 1, n, a[i].rank + 1, n);}printf("%lld\n", ans);} }總結(jié)
以上是生活随笔為你收集整理的【无码专区13】最小公倍数(线段树)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么修改dns设置(怎么修改dns设置密
- 下一篇: 设计公司业务怎么接来(设计公司业务怎么接