Educational Codeforces Round 114总结
緒論
https://codeforces.com/contest/1574/
以前想要打CF,總是覺得沒有時間,要做這個,要做那個,現在時間充裕了一些,想要多打一些CF,但是光打比賽不總結是沒有什么幫助的,這是我從以前的ACM訓練中吸取的慘痛教訓。從這篇文章開始準備好好總結一些比賽心得。
這場比賽是for div2的,因此對我來講有些難度,我發揮的不是很好,然后理所當然就掉分了(掉了50+,哭哭)
A
解題思路
要求構造正確的括號組合,即每一個左括號有一個相應匹配的右括號,要求給定括號對數n,輸出其n種組合方式。
剛開始思考了一下將左括號看作1,右括號看作-1,任何時刻整個表達式的和為非負數:要求每出現一個右括號的時候都有其對應的左括號。通過
這種方式進行構造。
但是很快,我發現其中的遞推關系:
一對括號:()
兩對括號:()()、(())
三對括號:()()()、(())()、((()))
我們不難發現,對n對括號的情況,其前2n-2個位置可以是n-1對括號的所有情況,然后最后再加一對括號
但是有一種情況不能由n-1對括號得來:最后兩個是))的時候,簡單起見,前n個都是(,后n個都是)顯然是一個解。
通過這種構造方法:Tn=Tn?1+1T_n = T_{n-1} + 1Tn?=Tn?1?+1,T1=1T_1 = 1T1?=1,我們總能夠造出n個解。因為n最多是50,我們只需要首先通過遞推構造出所有的解,然后直接輸出即可。
但是顯然n對括號不止n種解(當時比賽的時候心中有這個疑惑,但是沒有時間去仔細思考):
對三對括號而言,還有一種解:()(())。
AC代碼
// Copyright(C), Edward-Elric233 // Author: Edward-Elric233 // Version: 1.0 // Date: 2021/9/20 // Description: #include <vector> #include <string> #include <iostream>using namespace std;const int MAXN = 51; vector<vector<string>> ans;int main() {ios::sync_with_stdio(false);ans.push_back(vector<string>());ans.push_back(vector<string>());ans[1].push_back("()");for (int i = 2; i < MAXN; ++i) {ans.push_back(ans[i - 1]);for (auto &s :ans[i]) {s.append("()");}ans[i].push_back(string(i, '(') + string(i, ')'));}int T;cin >> T;while (T--) {int n;cin >> n;for (int i = 0; i < n; ++i) {cout << ans[n][i] << "\n";}}return 0; }B
解題思路
給定a、b、c個數的A、B、C,要求其相鄰兩個重復的次數為m次
我當時初步的想法是求出最小重復次數和最大重復次數,如果m在兩者之間則YES,否則則NO。
最大重復次數顯然是所有的A都出現完了后B再出現,然后C再出現,因為都放在一起出現一個重復的代價是1個字母(除了第一個),但是一旦A結束重復,還想A出現相鄰的重復,又要消耗一個沒有意義的字母作為第一個。
在求最小重復次數的時候我犯了錯導致WA了一發。當時想著,假設a<=b<=ca<=b<=ca<=b<=c,讓A、B、C循環出現,肯定先將A消耗完,然后B、C再重復出現,最后就只剩下C不得不重復,因此重復的個數是(c?a)?(b?a)?1=c?b?1(c-a)-(b-a)-1=c-b-1(c?a)?(b?a)?1=c?b?1
但是我忘記了,也有可能是A、B一起消耗C,這樣重復的個數是c?b?a?1c-b-a-1c?b?a?1,顯然比上面小。
但是有一個問題就是,如果a+b>ca+b>ca+b>c怎么辦?這個時候我們可以采取以下策略:首先讓A、B重復出現使得(a+b)(a+b)(a+b)每次減少2。因為當c?(a+b)c-(a+b)c?(a+b)為1或者0的時候都沒有重復,所以我們總能夠通過這種策略不出現重復,因此最小重復次數為max(c?a?b?1,0)max(c-a-b-1,0)max(c?a?b?1,0)
上面的思路最后是AC的。但是不免還有一個疑問?為什么我們能夠保證在minminmin和maxmaxmax之間的重復次數都能夠出現?
也就是說我們可以采取一種策略,對m個重復的排列C1C_1C1?,通過刪除插入將其轉換成m-1個重復的排列C2C_2C2?。
這種策略如下:我們可以將某個重復中的元素X取出,則重復變為m-1,剩下我們要做的就是將X再插入排列,我們只要插入和其左右都不同,且左右不同的一個位置。因為數據保證A、B、C都至少出現一次,且更少重復的排列是存在的,我們應該總能夠找到這樣一個位置。
好吧,我承認我有些證明不過來了,不過大概就是上面那樣。
AC代碼
// Copyright(C), Edward-Elric233 // Author: Edward-Elric233 // Version: 1.0 // Date: 2021/9/20 // Description: #include <iostream>using namespace std;int main() {ios::sync_with_stdio(false);int T, a, b, c, m;cin >> T;while (T--) {cin >> a >> b >> c >> m;if (a > c) std::swap(a, c);if (b > c) std::swap(b, c);if (a > b) std::swap(a, b);int min = std::max(c - b - a - 1, 0);int max = a + b + c - 3;if (m >= min && m <= max) {cout << "YES\n";} else {cout << "NO\n";}}return 0; }C
這道題比賽的時候沒做出來,但是我的思路是對的,只是在實現的時候當時已經十二點了,腦袋已經不轉了,糊涂了。也有一方面的原因是自己有一些想當然:對于一個數組a0,a1,a2,...,an?1a_0,a_1,a_2,...,a_{n-1}a0?,a1?,a2?,...,an?1?,和一個區間[a,b][a,b][a,b],我認為如果a0<ba_0 < ba0?<b且 an?1>aa_{n-1} > aan?1?>a則一定有元素在[a,b][a,b][a,b]區間內。。。現在發現這個錯誤后就AC了,嗚嗚嗚,如果這道題做出來說不定我都上分了。
解題思路
題目的意思是,有一個數組,其和為sum,要求ai+c1?x&sum?ai+c2?ya_i + c_1 \geqslant x \And sum-a_i+c_2\geqslant yai?+c1??x&sum?ai?+c2??y,求最小的c1+c2c_1+c_2c1?+c2?
通過對問題的分析,進行分類討論(看起來很復雜的問題有可能通過分類討論變得清晰起來)
這個時候無論取出哪個元素用來和x比較,都會導致sum更小,因此總需要花錢,
1.1. sum?xsum \leqslant xsum?x
說明任何一個元素都小于x,那么我們需要的錢為x?ai+y?(sum?ai)=x+y?sumx-a_i+y-(sum-a_i)=x+y-sumx?ai?+y?(sum?ai?)=x+y?sum。這真是令人振奮的消息,也就是說在這種情況下我們無論取哪個元素花費都是一樣的
1.2。 sum>xsum > xsum>x
這個時候問題又變得復雜起來了,如果里面小于等于x的元素,花費和上面一樣為y?sum+xy-sum+xy?sum+x,對于其中大于x的元素,需要的錢為y?sum+aiy-sum+a_iy?sum+ai?,也就是說aia_iai?越小越好,但還是大于xxx
綜上,當sum?ysum \leqslant ysum?y時,如果該數組中存在一個小于等于x的元素,最優解就為y?sum+xy-sum+xy?sum+x,如果全部都大于x,最優解為y?sum+min(ai)y-sum+min({a_i})y?sum+min(ai?),為了方便做到這一點,我們不妨對數組進行排序,通過判斷a0a_0a0?與x的大小判斷解為y?sum+xy-sum+xy?sum+x還是y?sum+a0y-sum+a_0y?sum+a0?
這個時候的情形更加復雜,因為存在可能不花錢的狀況。因此,我們不妨再對這種情況進行分類:
2.1. sum?ai?y&ai?xsum-a_i\geqslant y \And a_i \geqslant xsum?ai??y&ai??x
這種情況下我們不用付錢,要求?ai,x?ai?sum?y\exists a_i,x\leqslant a_i\leqslant sum-y?ai?,x?ai??sum?y
2.2. sum?ai<y&ai?xsum-a_i < y \And a_i \geqslant xsum?ai?<y&ai??x
這種情況要付錢y?sum+aiy-sum+a_iy?sum+ai?,要求?ai,ai?x&ai>sum?y\exists a_i,a_i\geqslant x \And a_i > sum - y?ai?,ai??x&ai?>sum?y
2.3. sum?ai?y&ai<xsum-a_i\geqslant y \And a_i < xsum?ai??y&ai?<x
這種情況要付錢x?aix-a_ix?ai?,要求?ai,ai<x&ai?sum?y\exists a_i,a_i < x \And a_i \leqslant sum - y?ai?,ai?<x&ai??sum?y
2.4. sum?ai<y&ai<xsum-a_i<y \And a_i <xsum?ai?<y&ai?<x
這種情況要付錢y?sum+ai+x?ai=x+y?sumy-sum+a_i+x-a_i=x+y-sumy?sum+ai?+x?ai?=x+y?sum,要求?ai,sum?y<ai<x\exists a_i,sum-y<a_i< x?ai?,sum?y<ai?<x
我們發現,在這種情況下另一個重要的量sum?ysum-ysum?y經常出現,因此我們另z=sum?yz=sum-yz=sum?y,然后將x和z之間的關系進行分類討論。
2.5.x?zx\leqslant zx?z
這個時候2.4不可能發生,只剩下了三種情況,我們可以分別對三種情況進行快速求解,對于滿足2.1的aia_iai?,ans=0ans=0ans=0,對于滿足2.2的aia_iai?,我們要找到滿足ai>za_i>zai?>z的最小aia_iai?,ans=ai?zans=a_i-zans=ai??z,對于滿足2.3的aia_iai?,我們要找到滿足ai<xa_i<xai?<x的最大aia_iai?,ans=x?aians=x-a_ians=x?ai?,即這個情況的解為三種解的最小值
2.6. x>zx > zx>z
這個時候2.1不可能發生,剩下三種情況的討論與2.5相同
上面的求值在一個排好序的數組中都可以使用lower_boundlower\_boundlower_bound和upper_boundupper\_boundupper_bound函數快速解決
每次查詢的復雜度為O(logn)O(log_n)O(logn?),總復雜度為O((n+m)logn)O((n+m)log_n)O((n+m)logn?),前者是排序的復雜度,這對2e52e52e5的復雜度是可以接受的
實現上面的程序需要對二分查找非常熟悉,但是自己實現的二分查找非常容易出現Bug,使用STL中的lower_boundlower\_boundlower_bound和upper_boundupper\_boundupper_bound就成了不二之選,這要求我們對這兩個函數非常熟悉。基本的用法很簡單,lower_boundlower\_boundlower_bound返回大于等于關鍵字的第一個迭代器,upper_boundupper\_boundupper_bound返回大于關鍵字的第一個迭代器,兩者之間的范圍就是等于關鍵字的范圍,如果要訪問元素一定要判斷是否等于尾后迭代器。如何求小于和小于等于有一個小技巧,對于小于,也就是大于等于的前一個元素,我們將lower_boundlower\_boundlower_bound向前移動一個就是最后一個小于關鍵字的元素,小于等于同理,不過我們需要注意的是要判斷迭代器是否是開始迭代器,如果是開始迭代器則說明不存在小于或者小于等于的元素。
這個思路是我在比賽的時候想出來的,后來再看發現還是很復雜,驚嘆自己當時竟然能夠想這么多。不過比較可惜的是在判斷2.1-2.4的時候我的腦袋已經糊涂了,導致最終沒能AC。這也提醒我千里之堤毀于蟻穴,行百里半九十,對一個程序來說每一個細節都是致命的,可能平時很瞧不起,覺得很簡單 ,但是他們其實是平等的,要對每一個小細節懷有敬畏之心。
AC代碼
// Copyright(C), Edward-Elric233 // Author: Edward-Elric233 // Version: 1.0 // Date: 2021/9/20 // Description: #include <vector> #include <array> #include <iostream> #include <algorithm> #include <climits>using namespace std;constexpr int MAXN = 2e5 + 5; using ll = long long; array<ll, MAXN> a; int n, m; ll x, y, sum, ans, z;int main() {ios::sync_with_stdio(false);cin >> n;sum = 0;for (int i = 0; i < n; ++i) {cin >> a[i];sum += a[i];}auto begin = a.begin();auto end = a.begin() + n;std::sort(begin, end);cin >> m;while (m--) {ans = LONG_LONG_MAX;cin >> x >> y;if (sum <= y) {if (a[0] > x) {ans = y + a[0] - sum;} else {ans = y + x - sum;}} else {z = sum - y;if (x <= z) {if (a[0] > z) {ans = a[0] - z;} else if (a[n - 1] < x) {ans = x - a[n - 1];} else {ans = LONG_LONG_MAX;auto it1 = std::upper_bound(begin, end, z);if (it1 != end) {ans = std::min(ans, *it1 - z);}auto it2 = std::lower_bound(begin, end, x);if (it2 != end && *it2 <= z) {ans = 0;}if (it2 > begin) {--it2;ans = std::min(ans, x - *it2);}}} else {if (a[0] >= x) {ans = a[0] - z;} else if (a[n - 1] <= z) {ans = x - a[n - 1];} else {ans = LONG_LONG_MAX;auto it1 = std::lower_bound(begin, end, x);if (it1 != end) {ans = std::min(ans, *it1 - z);}auto it2 = std::upper_bound(begin, end, z);if (it2 != end && *it2 < x) {ans = std::min(ans, x - z);}if (it2 > a.begin()) {--it2;ans = std::min(ans, x - *it2);}}}}cout << ans << "\n";}return 0; }…
后面的題目我沒有看,我發現做出來的人很少。也不準備去做,題目無窮無盡,不應該去追逐題目,而應該做一題會一題,從有限的題目中提升自己的思維能力。覺得做CF好像做智力游戲,也挺有意思的。至于數據結構、算法,可以刷紫書嘛。
總結
以上是生活随笔為你收集整理的Educational Codeforces Round 114总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vim命令笔记
- 下一篇: UVA - 210:Concurrenc