<style> .reveal .slides { text-align: left; font-size:35px } </style> # pretrain 4 Introduction to Competitive Programming 12/22 ---- * 常用函式 * 容器 * IO / 編譯優化 --- ## 常用函式 ---- * sort * nth_element * lower_bound / upper_bound * unique * * next / prev_permutation * * 字串/數字之間轉換 * ---- ### sort * 在標頭檔 algorithm 裡面 * 複雜度為 $O(NlgN)$ 參數是頭尾的 iterator ( first , last , compare ) * 此函式會將此區間的資料排序 * 使用方式為 ex : sort ( arr , arr+n , cmp ) 可以丟 vector , array , struct , deque等支援隨機存取的資料結構 * 於第三個函數傳入自訂比較函數compare ---- ### 範例 ```cpp= int arr[1e5+5]={}; bool cmp(int x,int y){ return x>y; } int main(){ for(int i=1;i<=100;i++) arr[i]=i; sort(arr+1,arr+100+1,cmp); } ``` 陣列結果 : 100,99,98,97,96,95..... ---- ### 例題: [[洛谷P1012 拚數](https://www.luogu.com.cn/problem/P1012)] 題意:給你N個數字把他拚成最大的數字 ```cpp= 3 13 312 343 會拼成 34331213 2 76 7 會拼成 776 ``` ---- ### 實作: ```cpp= bool cmp(string a,string b){ if(a+b > b+a) return 1; return 0; } ``` ---- ### nth_element * 在標頭檔 algorithm 裡面 * 複雜度為平均 $O(n)$ 參數是頭尾的 iterator ( first , target ( k ) , last ) * 此函式會將此區間第 k - first 小的數放在 k 位置,且 [first , k] 皆比 k 小,( k , last )皆比k大(但這兩個區間內是無序的) * 使用方式為 ex : nth_element(arr , arr+k , arr+n , cmp) 可以丟 vector , array , struct , deque 等支援隨機存取的資料結構 * 於第四個函數傳入自訂比較函數 compare ---- ### 範例 ```cpp= int arr[1e5+5]={}; bool cmp(int x,int y){ return x>y; //更改為降序排列 } int main(){ for(int i=1;i<=100;i++) arr[i]=i; nth_element(arr+1,arr+50+1,arr+100+1,cmp); } ``` 結果 : 100~51 (無序排列) , 50 , 49~1 (無序排列) ---- ### 例題: [[CF 412B](https://codeforces.com/problemset/problem/412/B)] 題意:給你 N 個數字,以及 K,求出第 K 大的數字是多少 ```cpp= 6 4 100 20 40 20 50 50 答案為40 ``` ---- ### 實作: ```cpp= int main(){ int n,k,a[100005]; cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i]; nth_element(a+1,a+k,a+n+1,greater<int>()); cout<<a[k-1]; return 0; } ``` ---- ### lower_bound / upper_bound * 在標頭檔 algorithm 裡面 * lower_bound ( first , last , value , cmp) upper_bound ( first , last , value , cmp) * lower : 尋找第一個不小於 value 的迭代器,若不存在則回傳 end * upper : 尋找第一個大於 value 的迭代器,若不存在則回傳 end * 以上兩個函數裡面所尋找的陣列必須要有經過排序(有單調性)才可以正常使用 * 時間複雜度 O(lg n) 兩者皆是 ---- ### 範例 ```cpp= using namespace std; int a[100005]; int main(){ int n,x; cin>>n; for(int i=0;i<n;++i)cin>>a[i]; sort(a,a+n); cin>>x; cout<<lower_bound(a,a+n,x)-a<<'\n'; cout<<upper_bound(a,a+n,x)-a<<'\n'; //通過返回的地址減去起始地址begin,得到索引值 return 0; } ``` ---- ### 例題: [[CF 493C](https://codeforces.com/contest/493/problem/C)] 題意:兩人比賽投籃,與正常不同的是,三分線與籃筐的距離 d 是不確定的。 首先輸入第一個人投了 n 個球,n 個球每個球投的時候他與籃筐的距離; 然後輸入第二個人投了 m 個球,m 個球每個球投的時候他與籃筐的距離。 ```cpp= 3 1 2 3 2 5 6 答案為9:6 5 6 7 8 9 10 5 1 2 3 4 5 答案為15:10 ``` ---- ### 實作: ```cpp= #include<iostream> #include<vector> #include<algorithm> using namespace std; int n,m; int a,b; vector<int> tf,ts; void judge(vector<int> &tx){ for(int i=0;i<n;i++){ int val1 = upper_bound(tf.begin(),tf.end(),tx[i])-tf.begin(); val1 = val1*2 + (n-val1)*3; int val2 = upper_bound(ts.begin(),ts.end(),tx[i]) - ts.begin(); val2 = val2*2 + (m - val2)*3; if(val1 - val2 == (a - b)){ if(val1 >a){ a = val1; b = val2; } } else if(val1 - val2 > a-b ){ a = val1; b = val2; } } } int main(){ cin>>n; tf.resize(n); for(int i=0;i<n;i++){ cin>>tf[i]; } cin>>m; ts.resize(m); for(int i=0;i<m;i++){ cin>>ts[i]; } sort(tf.begin(),tf.end()); sort(ts.begin(),ts.end()); if(n>=m) a = n*3 ;b = m*3 ; else a = n*2 ; b = m*2 ; judge(tf); judge(ts); printf("%d:%d",a,b); return 0; } ``` ---- ### unique * 在 <algorithm> 中 * 參數是兩個 iterator(first,last),將此區間連續相同的元素刪到只留一個,回傳刪完後最後一個沒被刪的元素的下一項的 iterator (可以想像成新的 last),但注意原陣列大小不會改變 * 複雜度 O(n) * 常用於離散化,把一個陣列中的資料從任意值域映射到 0∼C−1,其中 C 是相異數字數量,並保持原本的大小關係 <!-- 相異數字第k小 --> ---- ### 範例 ```cpp int a[9] = {1, 3, 3, 3, 4, 5, 6, 6, 1}; int n = unique(a, a + 9) - a; cout << n << '\n'; // 離散化後還剩多少元素 for(int i = 0; i < n; i++) { cout << a[i] << ' '; } ``` #### 輸出 ``` 6 1 3 4 5 6 1 ``` ---- 離散化程式碼 離散化三步驟: sort, unique, lower_bound ```cpp= //vector<int> a(n) = original array,tot; for(int i = 0; i < n; i++) tot.push_back(a[i]); sort(tot.begin(), tot.end()); tot.resize(unique(tot.begin(), tot.end()) - tot.begin()); for(int i = 0; i < n; i++) a[i] = lower_bound(tot.begin(), tot.end(), a[i]) - tot.begin(); ``` ---- ### 例題: 題意:你有一個很大的陣列,一開始陣列全部為 0,給 n 個區間 l, r ,要你在 l ~ r 的範圍加值 1,最後給你 q 次詢問,問你某個位置 p 的值是多少 - $0 \le l, r, p \le 10^{18}\ \ \ \ n,q \le 10^6$ ---- ### 想法 觀察 1:是全部操作都做完之後再查詢,因此可以用上上禮拜教的差分去求解 觀察 2:空間不夠開 $10^{18}$ 的陣列,因此需要用離散化,離散化後空間只需要 2 * n ---- ### 範例 code: ```cpp= int n, q; cin >> n >> q; vector<pair<long long,long long> > a; vector<long long> tot; for(int i = 0; i < n; i++) { int x, y; cin >> x >> y; a.push_back({x, y + 1}); tot.push_back(x); tot.push_back(y + 1); } sort(tot.begin(), tot.end()); tot.resize(unique(tot.begin(), tot.end()) - tot.begin()); for(int i = 0; i < n; i++){ a[i].first = lower_bound(tot.begin(), tot.end(), a[i].first) - tot.begin(); a[i].second = lower_bound(tot.begin(), tot.end(), a[i].second) - tot.begin(); } vector<int> cnt(tot.size() + 1); for(int i = 0; i < n; i++) { cnt[a[i].first]++; cnt[a[i].second]--; } while(q--) { int p; cin >> p; int pos = lower_bound(tot.begin(), tot.end(), p) - tot.begin(); cout << tot[pos] << '\n'; } ``` ---- ### next / prev_permutation * 在 <algorithm> 中 * 參數是兩個 iterator(first,last),將此區間改變成下/前個排列,若不存在則回傳 false,反之回傳 true * 均攤複雜度 $Ω(1)$,最差 $O(n)$ * next_permutation 產出的下一個排列都會比原本的字典序還大,而 prev 則相反 * 如果一開始的排列是字典序最小的,就可以用 next_permutation 產生出全部的排列。 ---- ### 範例 ```cpp= int a[3] = {1, 2, 3}; do{ for(int i = 0; i < 3; i++) cout << a[i] << ' '; cout << '\n'; } while(next_permutation(a, a + 3)); ``` #### 輸出: ``` 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 ``` ---- ### 字串/數字之間轉換 以下函式都是定義在 <string> 字串轉數字: stoi、stod、stoll、...(很多形態都有) 轉數字之後如果超過上界,就會回傳上界 數字轉字串: to_string (注意這要 c++11 以後才能用) ---- 範例 ```cpp 1000 -> to_string(1000) -> "1000" 11.11 -> to_string(11.11) -> "11.11" "1000" -> stoi("1000") -> 1000 ``` ---- --- ## 容器 ---- * Iterator * * pair/tuple * * vector * stack * queue * priority_queue * set * * map * * unordered_set / unordered_map * * multiset / multimap * * bitset * ---- ### 前情提要:模板 STL中的容器使用模板(template)來讓它能接受任意型態的資料,因此在宣告變數時,必須要告訴編譯器該容器是要裝什麼型態的資料。 c++的模板以 <> 來傳遞參數,例如 vector<int>、set<string,greater<string>> ---- ### Iterator 迭代器 迭代器是用來遍歷容器的東西,由於有些容器不像陣列能用下標 (index) 遍歷,迭代器便能用來遍歷這類結構。 ---- c++的迭代器皆被實作的與指標相似,用遞增運算子 (\++it / it\++) 來移動到下一項,且用反指標運算子(*it)來取得當前項的值。 多數還支援遞減運算子(- -it / it- -) 移動到前一項(稱作雙向迭代器) 有些支援 +/- 運算子(it+k / it-k) 一次移動多項(稱作隨機存取(random access)) ---- 對於一個容器,分別有代表頭尾的迭代器,其中頭代表首項,而尾代表最後一項的下一項,因此遍歷方式通常是for(it=begin;it!=end;it++) 對於陣列 (C-style array),其迭代器就是指標,且頭尾分別是arr/arr+n,對於STL內的容器,其迭代器是C::iterator,且頭尾分別是arr.begin() / arr.end() 此外STL內還有實作反向迭代器用來從最後一項開始往前遍歷,其形態是 C::reverse_iterator 頭尾分別是 arr.rbegin() / arr.rend() ---- ### pair/tuple 分別在 <utility> 和 <tuple> 中 偷懶用資料結構,分別是可以用來宣告裝 2 個東西/多個東西的變數 tuple 取用值略麻煩一點建議自己寫 struct ---- ### 使用範例:pair * 宣告 ```cpp pair<int,int> pair<double,int> ``` * 取值 ```cpp= pair<int,int> a a.first (可以取到第一個值) a.second (可以取到第二個值) ``` ---- ### 使用範例:tuple * 宣告 ```cpp tuple<int, int, int> tuple<double, int, long double, ...(還可以塞很多東西)> ``` * 取值 ```cpp= tuple<int, int, int> a get<0>(a) (可以取到第一個值) get<2>(a) (可以取到第三個值) ``` ---- ### vector * 在標頭檔 vector 裡面 * vector 裡面可以塞 int , string , long long 啥都可以塞 * 是動態陣列。有時候我們無法預估陣列所需要宣告的長度,又必須宣告陣列,這時會選用 vector * vector 的常用函式 : push_back , pop_back , size , empty , begin , end . ---- * 須注意的是若等到 vector 空間不夠裝,vector 會自動安排記憶體,但這麼做常數比較大,所以通常在使用前會先預設一些記憶體位置給 vector * 我們會使用 reserve() 。e.g. `vector.reserve(128)` * 或是直接在宣告時用建構元 (constructor) 指定大小。 e.g. `vector<int> vec(n)` ---- 實際演練 : 常用函式 ```cpp= #include <iostream> #include <vector> // vector 的標題檔 using namespace std; int main() { vector<int> Mega; //宣告一個 vector 名稱叫 Mega 用來裝 int 元素 //裡面的容器是空的 for (int i=1; i<=5; i++) Mega.push_back(i); for (int i=5; i>=1; i--) Mega.push_back(i); cout << Mega.size() << "\n"; for (int i=1; i<=4; i++) Mega.pop_back();// 將 vector 尾端元素刪掉 cout << Mega.size() << "\n"; cout << Mega.empty() << "\n"; // empty() 函式為查詢 vector 裡面是否有元素 // 回傳值為 bool 1 裡面沒有元素 0 則是有 Mega.clear(); cout << Mega.empty() << "\n"; } ``` ---- 實際演練 : 遍歷 陣列遍歷 ```cpp= #include <iostream> #include <vector> using namespace std; int main() { vector<char> roy; for (int i=0; i<26; i++) { char c = i + 'A'; roy.push_back(c); } for (int i=0; i<roy.size(); i++) { cout << roy[i] << " "; } cout << roy[5] << "\n"; cout << roy[0] << "\n"; // 查詢單一個元素可用陣列方式查詢 } ``` ---- 實際演練 : 遍歷 iterator 遍歷 ```cpp= #include <iostream> #include <vector> using namespace std; int main() { vector<int> vt; vector<int>::iterator iter; for (int i=95; i<=100; i++) { vt.push_back(i); } iter = vt.begin(); for(iter = vt.begin(); iter != vt.end(); iter++) { cout << *iter << "\n"; // 一定要 「 * 」 號 } } ``` ---- ### stack * 在標頭檔 stack 裡面 * stack裡面可以塞 int , string , long long 啥都可以塞 * 堆疊是一種先進後出的結構,像是疊盤子一樣,先放下去的會最慢被拿出來。 * stack的常用函式 : emplace(push) , size , empty , top , pop . * 不支援隨機存取:O(N) 查找速度慢:O(N) 在集合中增刪元素很快:O(1) * 常用在 DFS ---- 實際演練: ```cpp= #include <iostream> #include <stack> // stack 的標題檔 using namespace std; int main() { stack<int> Stk; //宣告一個 stack 名稱叫 stk 用來裝int元素 //一開始裡面的容器是空的 for (int i=4; i<=10; i++) { Stk.push(i); } cout << Stk.top() << "\n"; // 輸出 stack 最上層的元素,所以會輸出 10 Stk.pop(); // 刪除 stack 最上層的元素 cout << Stk.top() << "\n"; // 輸出 stack 最上層的元素,所以會輸出 9 cout << "裡面有多少元素" <<Stk.size() << "\n"; // 查詢 stk 裡面有幾個元素,裡面元素有 4 5 6 7 8 9共六個,所以會輸出 6 cout << Stk.empty() << "\n"; // empty() 函式為查詢 stack 裡面是否有元素 // 回傳值為bool 1 裡面沒有元素 0 則是 //清除 stack的元素 while(Stk.size() != 0) { // 或是 while(Stk.empty == 0) Stk.pop(); } cout << "裡面有多少元素" << Stk.size() << "\n"; cout << Stk.empty() << "\n"; } ``` ---- # 超\*經典例題 stack之括弧匹配 想法: 給予一個字串並包含了括號 '[', ']', '(', ')' 和 '{', '}',請驗證該字串中的括號是否合法配對。 也就是 "([])", "{()}", "[]" 為合法配對,但是 "(]", 或 "([)]" 就是不合法配對。 規律: * 括號數量一定是偶數 * 有左邊系列括號,就一定有右邊系列括號 * 越慢出現的左邊括號,他的右邊括號就越早出現(順序相反)。 ---- ### queue * 在標頭檔 queue 裡面 * queue裡面可以塞 int , string , long long 啥都可以塞 * 佇列是一種先進先出的結構,像是水管一樣的單向道,最早進去的會最先通出來。 * queue的常用函式 : push , size , empty , front , pop . * 不支援隨機存取:O(N) 查找速度慢:O(N) 在集合中增刪元素很快:O(1) * 常用在BFS ---- 實際演練: ```cpp= #include <iostream> #include <queue> // queue 的標題檔 using namespace std; int main() { queue<int> que; //宣告一個 queue 名稱叫 que 用來裝int元素 //一開始裡面的容器是空的 for (int i=4; i<=10; i++) { que.push(i); } cout << que.front() << "\n"; // 輸出 queue 最上層的元素,所以會輸出 4 que.pop(); // 刪除 queue 最上層的元素 cout << que.front() << "\n"; // 輸出 queue 最上層的元素,所以會輸出 5 cout << "裡面有多少元素" <<queue.size() << "\n"; // 查詢 queue 裡面有幾個元素,裡面元素有 5 6 7 8 9 10共六個,所以會輸出 6 cout << que.empty() << "\n"; // empty() 函式為查詢 queue 裡面是否有元素 // 回傳值為bool 1 裡面沒有元素 0 則是 //清除 queue的元素 while(que.size() != 0) { // 或是 while(que.empty == 0) que.pop(); } cout << "裡面有多少元素" << que.size() << "\n"; cout << que.empty() << "\n"; } ``` ---- ### 經典例題環節: 使用兩個 stack 去實作出 queue 的功能 作法: 始終維護 s1 作爲存儲空間,以 s2 作爲臨時緩衝區。 入隊時,將元素壓入 s1。 出隊時,將s1的元素逐個“倒入”(彈出並壓入)s2,將 s2 的頂元素彈出作爲出隊元素,之後再將 s2 剩下的元素逐個“倒回” s1。 ![](https://i.imgur.com/Tu7RPsb.png) ---- ### priority_queue * 在標頭檔 queue 裡面 * priority_queue 裡面可以塞 int , string , long long , 實作原理是 max_heap 在運作 * 為一序列容器 是一個帶優先級的 queue * priority_queue 的常用函式 : push , size , empty , top , pop . * 可以很快很方便的加入元素或取出當前最優先的元素 * 常用在各大題目 ---- ### priority_queue 複雜度分析 * push() 新增元素,複雜度 O(log n) * size() 取得目前元素數量,複雜度 O(1) * top() 取得目前最高優先(數值最大)的元素(不會刪掉),複雜度 O(1) * pop() 刪掉目前最高優先(數值最大)的元素,複雜度 O(log n) ---- 實際演練: ```cpp= #include <queue> priority_queue<int> pq; priority_queue<int, vector<int>, greater<int> > pq1; //從小到大 變成min_heap pq.push(5); pq.push(8); pq.push(4); // 元素可重複,會個別保留 pq.push(5); cout << pq.size() << "\n"; cout << pq.top() << "\n"; pq.pop(); cout << pq.size() << "\n"; cout << pq.top() << "\n"; cout << pq.empty() << "\n"; ``` ---- ### 題目演練 對每一個輸入,請輸出到現在為止已輸入的數的中位數。 ``` 輸入: 輸出: 1 1 3 2 4 3 60 3 70 4 50 27 2 4 ``` ---- ### 實際演練 ```cpp= void solve(){ long long x; priority_queue<long long> small; priority_queue<long long, vector<long long>, greater<long long>> big; while(cin >> x){ big.push(x); while(big.size() > small.size()){ small.push(big.top()); big.pop(); } while(!big.empty() && !small.empty() && big.top() < small.top()){ ll a = big.top(), b = small.top(); big.pop(), small.pop(); big.push(b), small.push(a); } if(small.size() > big.size()) cout << small.top() << endl; else cout << (small.top()+big.top())/2 << endl; } ``` ---- ### set 集合 * 在標頭檔 <set> 裡面 * 可以插入值,及查詢值有沒有在集合裡面 * set 是有序的,對 set 遍歷時,會以定義的大小順序遍歷,預設是由小到大 * 大多數操作皆為 $O(lgN)$ * set 的常用函式:insert, erase, count, size, empty, begin, end, lower_bound, upper_bound ---- ### 使用範例: ```cpp set<int> s; // [] s.insert(1); // st = [1] s.insert(5); // st = [1, 5] s.insert(3); // st = [1, 3, 5] s.insert(2); // st = [1, 2, 3, 5] s.insert(1); // st = [1, 2, 3, 5] ``` ---- ### 使用範例: ```cpp set<pair<int,int> > s; //[] s.insert({1, 2}); // [{1, 2}] s.insert({2, 1}); // [{1, 2}, {2, 1}] s.insert({2, 1}); // [{1, 2}, {2, 1}] s.insert({3, 5}); // [{1, 2}, {2, 1}, {3, 5}] s.count({2, 1}); //return 1; ``` ---- ### map * 在標頭檔 <map> 裡面 * 映射,基本上就是多了個對應值的set,單位型態為 pair<key,value> * 用法跟 set 很像,但多了 operator[] 可以取值或存值。 * 大多操作皆為 $O(lg\ n)$ * map 的常用函式:insert, erase, count, size, empty, begin, end, lower_bound, upper_bound. ---- ### 使用範例: ```cpp map<int, int> mp; mp[3] = 1; //[3] = 1, mp[2] = 2; //[2] = 2, [3] = 1; mp[3] = 5; //[2] = 2, [3] = 5; ``` <!-- 加個 string 當 key 的粒子--> ---- ### 使用範例: ```cpp map<string,int> mp; // map 還可以用 string 當 key mp["qwer"] = 1; mp["abc"] = 2; ``` ---- ### unordered_set / unordered_map * 基本上就是無序的 set / map * 由於是由 hash 實作,複雜度最好是 $O(1)$,但也可能到 $O(N)$ * 當不需要資料有序時可以使用 * 使用時候要小心,會被刻意生出來的測資卡,在有 hack 的比賽不建議使用 * 函式與 set / map 類似,只是少了 lower_bound, upper_bound. ---- ### multiset / multimap * key 可以重複的 set / map * 由於 key 會重複,因此少了 [ ]operator * 大多操作皆為 $O(lg\ n)$ * 函式與 set / map 類似 ---- ### 使用範例: ```cpp multiset<int> s; //[] s.insert(1); // [1] s.insert(2); // [1, 2] s.insert(3); // [1, 2, 3] s.insert(1); // [1, 1, 2, 3] s.count(1); //return 2; ``` ---- ### 使用範例: ```cpp multimap<int, string> mp; //[] mp.insert({1, "c8763"}); mp.insert({1, "38763"}); mp.insert({2, "1234"}); mp.insert({4, "mpmp"}); mp.count(1); // return 2 ``` ---- ### bitset * 可看作是 bool 陣列,但長度必須是(編譯時期)常數 (constexpr) * 支援位元運算,且成員函數多為位元相關函數 * 用在位元運算常數非常小 * 沒有要做位元運算別亂用(用 vector<bool> 就好) * 函式:count、size、test、any、none、all、set、reset、flip。 ---- ---- ### 使用範例: ```cpp bitset<10> a; bitset<10> b(10); bitset<10> c("10010"); ``` --- ## IO / 常數優化 ---- * IO 優化 * 常數優化 ---- ### 什麼是優化? 保持語義不變的情況下,對程式運行速度、程式可執行文件大小作出改進 ---- in/out優化 這個函數是一個“是否相容 stdio”的開關,C++ 為了相容 C,保證程式在使用了 printf 和 std::cout 的時候不發生混亂,將輸出綁到了一起。同步的輸出是安全的。 這其實是 C++ 為了相容而採取的保守措施,也是使 cin/cout 速度較慢的主要原因。我們可以在進行 IO 操作之前將 stdio 解除綁定 但是在這樣做之後要注意不能同時使用 std::cin 和 scanf,也不能同時使用 std::cout 和 printf,但是可以同時使用 std::cin 和 printf,也可以同時使用 scanf 和 std::cout。 ```cpp= ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0); ``` ---- ## 強常數優化 但不是萬能的 ```cpp= #pragma GCC optimize("O3,unroll-loops") #pragma target optimize("avx2,bmi,bmi2,lzcnt,popcnt") ``` 網上針對 pragma GCC optimize 的文章有非常多,隨便搜尋都有一大堆,每個人都會有自己的習慣跟信仰,並不代表這邊就是最好的優化,也只是個人的常用而已。 ---- ---- ## Question Time and Practice 今天的課程也可以上 [cpprefference](https://cplusplus.com/reference/) 去看容器或者是函式的詳細內容,由於篇幅關係,可能還有一些函式沒有說到。
{"metaMigratedAt":"2023-06-17T16:46:10.382Z","metaMigratedFrom":"YAML","title":"pretrain 4","breaks":true,"contributors":"[{\"id\":\"19f09ccf-6b99-452f-971f-955cfc1657f3\",\"add\":236,\"del\":35},{\"id\":\"5df14ba0-4dd2-4172-b309-8b5af9ede7a1\",\"add\":7234,\"del\":844},{\"id\":\"e8edfe24-eef8-448f-beff-2798a40d21cd\",\"add\":96,\"del\":16},{\"id\":\"e4a3af8b-860c-46a0-96f1-558667fdcf41\",\"add\":13013,\"del\":3277}]"}
    973 views
   Owned this note