[ACM] 訓練賽題解
2021_積分賽第二場
名稱 | 來源 | 演算法 |
---|---|---|
敵兵佈陣 | HDU-1166 | 線段樹(簡單) |
Emoticons | ICPC Central Russia Regional Contest (CRRC 19) | map 模擬 |
Power play | ICPC Central Russia Regional Contest (CRRC 19) | 浮點二分(卡精度) |
Prinzessin der Verurteilung | CodeForces 1536B | 思維題 |
Vases and Flowers | HDU 4614 | 線段樹 + 二分 |
Omkar and Bad Story | CodeForces 1536A | 思維題 |
A - 敵兵佈陣
題解
題意中文沒啥好說的,多組測試樣例,對輸入的陣列進行區間查詢,單點修改,就是專門留給你們的線段樹板子題。
#include <bits/stdc++.h> using namespace std; const int N = 50005; struct Node { int l,r; int sum; }T[N<<2]; int a[N]; void push_up(int rt) { T[rt].sum = T[rt << 1].sum + T[rt << 1|1 ].sum; } void build(int rt,int l,int r) { T[rt] = {l,r,0}; if( l == r ){ T[rt].sum = a[l]; return ; } int mid = (l + r) >>1; build(rt << 1,l,mid),build(rt << 1|1,mid + 1,r); push_up(rt); } void update(int rt,int pos ,int val) { if( T[rt].l == T[rt].r ){ T[rt].sum += val; return ; } int mid = (T[rt].l + T[rt].r) >>1; if( pos <= mid ) update(rt << 1,pos,val); else update( rt << 1|1,pos ,val ); push_up(rt); } int query(int rt,int l,int r) { if( l <= T[rt].l && r >= T[rt].r ){ return T[rt].sum; } int mid = ( T[rt].l + T[rt].r ) >> 1; int son = 0; if( l <= mid ) son += query(rt << 1,l,r); if( r > mid ) son += query(rt << 1|1,l,r); return son; } void solve(){ int n; cin >> n; for(int i = 1 ; i <= n ; i++){ cin >> a[i]; } build(1,1,n); string op; while( cin >> op ){ if( op == "End" ) break; int x,y; cin >> x >> y; if( op == "Query" ){ cout << query(1,x,y) <<"\n"; }else if( op == "Add" ){ update( 1,x,y ); }else{ update( 1,x,(-1)*y); } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); int t; cin >>t; for(int i = 1 ; i <= t ; i++){ cout << "Case "<<i<<":"<< "\n"; solve(); } return 0; }
B - Emoticons
題意
一個叫 Basil 的程式設計師,正在寫一個新的文字編輯器。為了檢視郵件,Basil編寫了一個特殊的模組,該模組從文字檔案中提取所有表情符號,並將它們放在單獨的行中顯示。不幸的是,模組中出現了一個錯誤,表情符號中的字元混淆了。
Basil 知道以下的這些表情都應用到了信件中:
;-)
,;-(
,:)
,:(
,:-\
,:-P
:D
,:C
,:-0
,:-|
,8-0
,:-E
%0
,:-X
,:∼(
,[:|||:]
幫助 Basil, 寫一個程式,把讀入的亂序表情,恢復正常。如果有幾種可能的恢復選項,那麼其中任何一種都是正確的。
題解
就是一道大模擬題,用map來存每個字元出現了多少次。
由分析後我們可以知道,有的表情的組合優先順序是高於其他表情的(也就是說,我們在組合的時候要優先去組合他)。
有的符號是獨一無二的,他只要出現,就唯一對應一個表情,那麼其他非唯一的表情就是肯定會給他優先消耗。就比如說
D
,C
,P
,8
... 只要這些字元出現,那麼它就唯一的對應:D
,:C
... 這些表情,我們此時就把這個表情所涉及到的字元都在map中 -- ,就好了。 注:
\
字元,要兩個才可以\\
,此為轉義字元 注意:這段程式碼非常非常繁瑣,模擬題就是來噁心人的,你wa了,就是細節錯了,我們當時做的時候,他們都放棄了,我最後看了半天才發現那裡錯。
#include <bits/stdc++.h> using namespace std; const int N = 1e3 + 5; int a[N]; int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); string s; cin >> s; int len = s.size(); int cnt = 0; map<char, int> ma; ma['['] = 0, ma[':'] = 1; ma[']'] = 2, ma['|'] = 3; ma['D'] = 4, ma['C'] = 5; ma['P'] = 6, ma['E'] = 7; ma['8'] = 8, ma['0'] = 9; ma['%'] = 10, ma['X'] = 11; ma['~'] = 12, ma['\\'] = 13; ma['-'] = 14, ma['('] = 15; ma[';'] = 16, ma[')'] = 17; for(int i = 0; i < len; i ++ ){ a[ma[s[i]]] ++; } while(a[0]){ cout << "[:|||:]" << endl; a[0] --, a[1] -= 2, a[2] --; a[3] -= 3; } while(a[4]){ cout << ":D" << endl; a[4] --, a[1] --; } while(a[5]){ cout << ":C" << endl; a[5] --, a[1] --; } while(a[6]){ cout << ":-P" << endl; a[6] --, a[1] --, a[14] --; } while(a[7]){ cout << ":-E" << endl; a[14] --, a[1] --, a[7] --; } while(a[3]){ cout << ":-|" << endl; a[3] --, a[1] --, a[14] --; } while(a[8]){ cout << "8-0" << endl; a[8] --, a[14] --, a[9] --; } while(a[10]){ cout << "%0" << endl; a[9] --, a[10] --; } while(a[9]){ cout << ":-0" << endl; a[9] --, a[1] --, a[14] --; } while(a[11]){ cout << ":-X" << endl; a[11] --, a[1] --, a[14] --; } while(a[12]){ cout << ":~(" << endl; a[12] --, a[1] --, a[15] --; } while(a[13]){ cout << ":-\\" << endl; a[13] --, a[1] --, a[14] --; } while(a[14] && a[16] && a[15]){ cout << ";-(" << endl; a[16] --, a[14] --, a[15] --; } while(a[14] && a[16] && a[17]){ cout << ";-)" << endl; a[16] --, a[14] --, a[17] --; } while(a[1] && a[15]){ cout << ":(" << endl; a[1] --, a[15] --; } while(a[1] && a[17]){ cout << ":)" << endl; a[1] --, a[17] --; } cout << "LOL" << endl; return 0; }
C - Power play
題意
basil 在分析一個數學問題的時候發現一個有趣的現象, 對於數字 2,4 滿足 $ 2^4 = 4^2$ 。他認為這對程式設計設計大賽的參賽者來說可能是一個巨大的挑戰。不幸的是,Basil 不能發現另外的這樣的數對了,他認為對於滿足這種關係的數對,他只能找到2,4。好吧,那我們就改變條件,有三個數字。Basil 決定。編寫的列舉選項程式證實了三個數字的任務是有意義的。
你的任務是:找到一個 x 其範圍在 \(1 \le x\le1e^{18}\) ,滿足 $ a^x = x^b$ (a,b為輸入的數字),答案可能有很多任意輸出滿足條件的答案即可,如果沒有這樣的數存在那麼輸出0 。
題解
由題目我們得到這個公式
\[a^x = x^b \] 我們對兩邊取對數得到
\[x\times log(a) =b\times log(x) \] 移項可得
\[x = \frac{ b \times log(x) }{log(a)} \] 然後我們二分x的值,就可以了
單調性證明略
\[ \]#include <bits/stdc++.h> using namespace std; typedef long long LL; LL a, b; LL f(LL l, LL r) { if (l > r) return 0; LL mid = (l + r) / 2; double s=double(log(mid)/log(a)); if (abs(s * b - mid) < 0.00000000001) ///看是否有比他更小的存在 { LL ans = f(1, mid - 1); if (ans) return ans; return mid; } if (mid > s * b) return f(l, mid - 1); return f(mid + 1, r); } int main() { int flag = 1; scanf("%lld%lld", &a, &b); printf("%lld\n", f(1, 1e18)); }
D - Prinzessin der Verurteilung
題意
給你一個字串,讓你按字典序求第一個沒在這個字串中出現的子串,(由小到大排列的順序為
a
b
c
d
e
f
...z
...aa
ab
ac
ad
... ),在樣例qaabzwsxedcrfvtgbyhnujmiklop
中,a
到z
都 出現了,ab
出現了,ac
沒有出現,那麼答案就是ac
題解
因為資料範圍不大,所以我們直接上 STL 就好了 ,
string
的find
函式可以解決 我們建立一個
vector<string >
陣列來遍歷的字典序字串,然後對於每個字串我們都在其後面26個字母 對於
""
空字串,我們加26個字母后變成,a
,b
,c
,...,z
,對於a
字串我們加了26個字串後變成aa
,ab
,ac
,...,az
以此類推。#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 10; /// 遍歷的模板 string str = "abcdefghijklmnopqrstuvwxyz"; void solve() { int n; cin >> n; string s; cin >> s; /// 建立字串陣列,同時把第一個字串初始化為空字串 vector<string> bfs = {""}; for(int i = 0; i < bfs.size() ; i++){ string te = bfs[i]; /// 找到了就直接輸出 if( s.find(te) == string::npos ){ cout << te << "\n"; break; } /// 對於每個字串,我們都在起後面加上26個字母 for( auto it : str ){ bfs.push_back(te+it); } } } int main() { ios::sync_with_stdio(false); cout.tie(nullptr); cin.tie(nullptr); int t; cin>> t; while(t--){ solve(); } }
E - Vases and Flowers
題意
給定一個整數n, 表示有n個花瓶(初始為空花瓶), 編號從0~n-1. 有如下兩種操作:
①從編號為x的花瓶開始, 要放y朵花, 從前往後一次遍歷, 如果是空花瓶則放一朵花在裡面, 直至放完所有花或者遍歷到最後一個花瓶為止. 倘若此時還有花放不下, 則將它們直接丟棄.
②清理[l, r]區間的所有花瓶, 如果裡面有花則將其丟棄 對於每個操作①, 需要輸出第一個放花的位置和最後一個放花的位置. 倘若一朵花都放不下, 需要輸出"Can not put any one."
對於每個操作②, 需要輸出該區間被清理的花的數量。題解
我們用線段樹來維護這個花瓶,線段樹的每一個結點,就相當於一個花瓶。
這道題最關鍵的點就是,如何設定狀態,我們用
sum
來表示區間的剩餘可插花數,用lazy來標記區間修改的操作lazy = -1,
為不變,lazy = 0
清除插花,lazy = 1
待插花。 對於操作①,我們線上段樹上遞迴的進行二分,如果當前區間在我們需要插花的區間,並且
可插花數 <= 剩餘的待插花樹
,那麼我們就在這個區間中二分的查詢需要最左邊的插花位置,和最右邊的插花位置,並與全域性變數 L,R 比較,最後輸出這次的L,R。 對於操作②,相當於rangeQuery + rangeUpdate,在更新區間插花的同時,計數有那些結點插了花,並返回。
#include <bits/stdc++.h> using namespace std; const int N = 5e5 + 10; struct Node { int l,r; int sum,lazy; ///sum為當前剩餘可插畫數 ///lazy = -1,為不變,lazy = 0清除插花,lazy = 1待插花 }t[N << 2]; int L,R; void push_up(int rt){ t[rt].sum = t[rt << 1].sum + t[rt << 1|1].sum; } void build(int rt,int l, int r) { t[rt] = {l,r,1,-1}; if( l == r ) return ; int mid = (l + r) >> 1; build(rt << 1,l,mid),build(rt<<1|1,mid +1 ,r); push_up(rt); } void push_down(int rt) { if( t[rt].lazy == -1 ) return; int lazy = t[rt].lazy ,l = t[rt].l,r = t[rt].r; int mid = l + r >> 1; t[rt<<1].sum = lazy * (mid - l +1); t[rt<<1|1].sum = lazy * (r - mid); t[rt << 1|1].lazy = t[rt << 1].lazy = lazy; t[rt].lazy = -1; } ///查詢插花區間最左邊的位置 int findLeft(int rt) { if( t[rt].l == t[rt].r ) return t[rt].r; push_down(rt); if( t[rt << 1].sum != 0 ) findLeft(rt << 1); else findLeft(rt << 1|1); } ///查詢插花區間最右邊的位置 int findRight(int rt) { if( t[rt].l == t[rt].r ) return t[rt].l; push_down(rt); if( t[rt << 1|1].sum != 0 ) findRight(rt <<1|1); else findRight(rt << 1); } void rangeUpdate(int rt,int l,int r,int &val) { if( val == 0 || t[rt].sum == 0 ) return; if( l <= t[rt].l && r >= t[rt].r && t[rt].sum <= val ) { val -= t[rt].sum; /// 更新第一個和最後一個 L = min(L,findLeft(rt)) , R = max(R,findRight(rt)); t[rt].sum = 0; t[rt].lazy = 0; return ; } int mid = t[rt].l + t[rt].r >> 1; push_down(rt); if( l <= mid ) rangeUpdate(rt << 1,l,r,val); if( r > mid ) rangeUpdate(rt << 1|1,l,r,val); push_up(rt); } int rangeDelte(int rt,int l,int r) { if( l <= t[rt].l && r >= t[rt].r ) { int res = t[rt].r - t[rt].l + 1 - t[rt].sum; t[rt].sum = t[rt].r - t[rt].l + 1; t[rt].lazy = 1; return res; } int mid = (t[rt].l + t[rt].r) >> 1; int res = 0; push_down(rt); if( l <= mid ) res += rangeDelte(rt << 1,l,r); if( r > mid ) res += rangeDelte(rt << 1|1,l,r); push_up(rt); return res; } int main() { ios::sync_with_stdio(false); cin.tie(0); int n,t,m,x,y,cmd,te; cin >> t; while(t--) { cin >> n >> m; build(1,1,n); while(m--) { cin >> cmd >> x >> y; if(cmd == 1) { L = n + 1, R = 0,te = y; rangeUpdate(1,x+ 1,n,y); if(te == y) cout << "Can not put any one." <<endl; else cout << L - 1 << ' ' <<R - 1 <<endl; } else cout << rangeDelte(1,x + 1,y + 1) << endl; } cout <<endl; } return 0; }
F - Omkar and Bad Story
題意
Omkar收到了來自 Anton 的訊息 "你對問題A的解釋很混亂。再寫個詳細的說明 " 。正因如此, Omkar 給了你一個數組 a ,其有 n 個不同的數。如果一個數組(b)滿足任意的兩個元素 \(b_i,b_j\) ,\(|b_i - b_j|\) 在這個陣列中至少出現了一次 ,那麼這個陣列就是一個nice 陣列。此外,b中的所有元素必須是不同的。你能新增幾個整數(可能是0)到a來建立一個大小不超過300的陣列b 嗎。如果a已經是個 nice陣列了,你就不需要新增任何元素。
題解
如果原本數列中出現了負數,則直接輸出NO,因為你有一個負數存在的情況下
假設原數列為1 和-1
-1 - 1的絕對值為2
-1 - 2的絕對值為3
可以看出新出現的數只會不斷變大,所以是個無解保證數列為正數的情況下直接輸出0至100就好了,
優化一下就是輸出最大的數
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 10; void solve() { int n; cin>>n; int a[n]; bool sanu=false; int maxi=INT_MIN; for(int i=0; i<n; i++){ cin >> a[i]; maxi=max(maxi, a[i]); if(a[i] < 0) sanu = true; } if(sanu){ cout << "NO\n"; continue; }else { cout << "YES\n"; cout << maxi+1 << endl; for(int i = 0;i <= maxi ; i++) cout << i << " "; cout << endl; } } int main() { ios::sync_with_stdio(false); cout.tie(nullptr); cin.tie(nullptr); int t; cin>> t; while(t--){ solve(); } }