ACM解题的一些技巧和方法
參加acm也一段時間了,決定重新總結下自己學到的一些東西,這樣才會收獲得更多。
比賽考驗的不僅僅是知識面,思維能力,編碼能力,更有團隊合作的意識以及心態。平時接觸的題目會涵蓋各個方面的內容,我們都需要有一些適當的解題技巧,在這里總結談一些比較重要的技巧或者方法的運用:1、預處理 2、算法優化 3、STL的運用。
我只是根據自己的學習到的東西來粗略地總結總結,還遠沒有達到那種真正會的水平,如果有任何錯誤或者說得不好的地方請見諒(路過的大牛、大神請無視我.....)
?
編程的技巧著實很多,小到一些基本的簡單操作比如說位運算的運用大到一些好的思想的應用。任何一個技巧的應用都可能給你的程序帶來不一般的在速度或其他方面的變化,就比如說這個位運算符的應用,充分利用的話時間開銷大大減小。
我也曾在一本叫做算法心得的書上看到了這個技巧,老外寫的,非常經典,書沒有說一些我們現在的算法,而是說怎么利用一些簡單卻非常高效的技巧,只是那本書我實在沒有看完…感覺寫的不錯,但我還沒有用過。
?
下面也有一些小技巧(在此引用,感謝這位博主):
http://blog.sina.com.cn/s/blog_a46ca35201013c4n.html
?
下面進入正題:
1、預處理
所謂預處理,顧名思義,就是事先計算好需要的值或事先處理某些東西,有時候你會發現你做一個題目出現了TLE,原因就是重復的計算會導致效率不高(或者說你的預處理不夠“優雅”)。
一、直接把結果預處理:
舉幾個比較簡單的例子:
1、Xtuoj1052
Description
某一個數字集合定義如下:
1. 0屬于這個集合;
2.如果x屬于這個集合,那么2x+1,3x+1也屬于這個集合;
3.集合只包含按增序排列的前100000個元素。
集合按增序排列,根據輸入的元素序號,輸出對應的元素值。
輸入
每行一個整數n(n<100000),表示元素的序號(從0開始記數),如果是-1,則輸入結束。
輸出
每行輸出對應元素的值。
Sample Input
0
1
2
3
4
5
-1
Sample Output
0
1
3
4
7
9
分析:很明顯,不能也不好直接判斷是否存在于這個集合中,只需要把所有存在于這個集合中標記,并且預處理這些元素的序號,之后輸出就行了,那么一次預處理便可以知道所有序號對應的元素了。
#include <iostream> #define MAX 2000001 using namespace std;int a[100010], b[3*MAX]; int main() {int n, i, j;b[0] = 1;for (i = 0; i < MAX; i++)if (b[i] == 1) b[2*i+1] = b[3*i+1] = 1;for (i = 0, j = 0; i < 100000; j++)if (b[j] == 1) a[i++] = j;while (cin >> n, n != 1) cout << a[n] << endl;return 0; }2、POJ1426
點擊打開鏈接
題意:有k個壞人k個好人坐成一圈,前k個為好人(編號1~k),后k個為壞人(編號k+1~2k),給定m,從編號為1的人開始報數,報到m的人就要自動死去,之后從下一個人繼續 開始新一輪的報數。問當m為什么值時,k個壞人會在好人死亡之前全部死掉?
分析:遇到存在環的題目的時候,可以直接直線化處理。當然也可以直接利用循環鏈表或者數組進行環的模擬,不過這樣的程序寫起來有點復雜。
這個題目直接暴力模擬求解必定TLE,需要一點數學的知識,這在里就不詳細說了,即使這樣,還是會超時,正確的方法便是預處理出僅有的14個答案,但既然已經知道了所有答案,而且又只有14個,那么直接把答案交上去就行了。
#include <cstdio>int ans[15] = {0, 2, 7, 5, 30, 169, 441, 1872, 7632, 1740, 93313, 459901, 1358657, 2504881, 13482720}; int main() {int n;while (scanf("%d", &n), n) printf("%d\n", ans[n]);return 0; }3、uva12716
點擊打開鏈接
題意:給定一個整數n,求出有多少對整數a,b滿足1<=b<=a<=n且gcd(a,b)=a XOR b.
分析:最容易想到的方法是枚舉a,b,雙重循環加上求gcd,總復雜度為O(n*n*logn),絕對無法承受。如何減少枚舉呢?注意到亦或運算的性質,如果a^b=c,那么a^c=b,既然c為a,b的最大公約數的話,那么我們可以從枚舉a和c出發,那么就是枚舉所有因子c及其可能的倍數a,和素數篩法一樣,這樣復雜度為O(nlogn*logn),n最大為30000000,復雜度還是有點高,怎么減少復雜度呢?這就要通過一點數學知識或者找規律發現了,通過打印出所有滿足條件的a,b,c可以發現a+b=c,所以可以將復雜度降為O(n*logn),但是題目是多樣例輸入,如果每次都需要O(n*logn)計算答案的話,還是會超時,觀察便可得知其實在計算n以內滿足條件的a,b對數時比n小的數以內的對數都已經計算出來了,也就是說不需要重復計算了,那么我們可以通過一次預處理,在計算的過程中統計每個a組合時的對數,之后循環遍歷一次全部加起來就可以知道每個n以內的答案了。
代碼:
#include <cstdio> #include <algorithm> #include <cstring> #include <cmath> using namespace std;const int N = 30000000; int a[N+5]; void pretreat() {for (int i = 1; i <= 15000000; i++) {for (int j = i<<1; j <= N; j += i) {if ((j ^ (j-i)) == i) a[j]++;}}for (int i = 2; i <= N; i++) a[i] += a[i-1]; }int main() {pretreat();int t, ca = 0;scanf("%d", &t);while (t--) {int n;scanf("%d", &n);printf("Case %d: %d\n", ++ca, a[n]);}return 0; }二、把需要用的預處理
比較常見的基本就是三個:預處理素數、預處理組合數、預處理前綴和。
首先舉個比較經典的例子:素數打表
判斷是否素數有3種方式:O(sqrt(n)的簡單素性測試、埃氏篩法,以及Miller_Rabin 算法進行素數測試。
如果需要進行大量的用到素數或者判斷素數,則可以埃氏篩法打表出所有的素數。
1、xtuoj1237
Description
題目描述
如果n和n+2都是素數,我們稱其為孿生素數,比如3和5,5和7都是孿生素數。給你一個區間[a,b],請問期間有多少對孿生素數?
輸入
第一行是一個整數K(K≤ 10000),表示樣例的個數。以后每行一個樣例,為兩個整數,a和b,1≤a≤b≤5000000。
輸出
每行輸出一個樣例的結果。
樣例輸入
5
1 3
1 10
1 100
1 1000
1 5000000
樣例輸出
0
2
8
35
32463
分析:計算區間內個數的題目一般滿足區間減法性質,但是不能一概而論,具體題目具體分析,就像這題一對孿生素數是跨越了3個數,要分情況考慮。
首先直接標記出所有的素數,令g[x]為1到x+2這個區間內孿生素數的對數,要統計出數量,遍歷一次即可,只需要一次預處理就可以計算出所有的g[x],之后便可以O(1)計算出所有1到x+2這個區間內孿生素數的對數了。
如果輸入的區間長度小于2,那么必定沒有,如果長度大于2,稍加思考便可以得知答案即為g[b-2]-g[a-1]。
#include <cstdio> #include <cmath>const int N = 5000001; int f[N], g[N]; int main() {int up = sqrt(N);for (int i = 2; i <= up; i++)if(!f[i]) for (int j = i*i; j <= N; j += i) f[j] = 1;for (int i = 3; i < N-1; i += 2)g[i+1] = g[i] = g[i-1] + !(f[i]|| f[i+2]);int t;scanf("%d", &t);while (t--) {int a, b;scanf("%d %d", &a, &b);b-a < 2 ? puts("0") : printf("%d\n", g[b-2] -g[a-1]);}return 0; }2、CF231CTo Add or Not to Add
點擊打開鏈接
題意:給定一個數組,每次可以給任意元素加1,這樣的操作不能超過k次,求操作不超過k次后數組中一個數能夠出現的最多次數,以及能夠以這個次數出現的最小的數。
分析:這個題目明顯具有單調性,這樣的話就可以進行二分搜索求取最大次數了。怎么判斷假定的解是否可行呢?既然只能是加1,而且又不超過k次,那么首先將數組排序,假設搜索的次數為m,那么i從第m個數循環到最后一個數,只要滿足了次數不小于k就立即退出循環,這樣找到的便一定是出現m次的最小的數,但是這個判斷的過程就是第m個數與其之前的m-1個數的差之和要不大于k,如果每次都直接加上差進行判斷必定超時,因為二分搜索加循環判斷的時間復雜度太高,那么最好的優化就是直接之前預處理,求出第1個數到第m個數區間的和,后面判斷的時候直接就是o(1)計算區間的和了。
?
代碼如下:
#include <cstdio> #include <algorithm> #include <cstring> using namespace std;typedef long long LL; const int INF = 0x3f3f3f3f; const int N = 100010; LL a[N], sum[N];int main() {int n; LL k;while (~scanf("%d %I64d", &n, &k)) {for (int i = 1; i <= n; i++) scanf("%I64d", &a[i]);sort(a + 1, a + 1+n);int r = INF, l = 0;sum[1] = a[1];for (int i = 2; i <= n; i++) sum[i] = a[i] + sum[i-1];LL ans;while (r - l > 1) {int m = (r+l) / 2;if (m > n) { r = m; continue; }int flag = 0;for (int i = m; i <= n; i++) {if ((m-1)*a[i] - sum[i-1] +sum[i-m] <= k){flag = 1; ans = a[i];break;}}flag ? l = m : r = m;}printf("%d %I64d\n", l, ans);}return 0; }
三、關于預處理的總結:
預處理的目的是為了減少重復的計算從而減少時間復雜度,有時一個簡單的預處理能使得算法性能顯著提高。首先我們可以按思路直接寫一個程序,如果復雜度太大,那么算法的優化可以從這個過程出發,思考可以對哪個算法的部分進行改進,而預處理便是一種對應的非常重要的技巧。像預處理區間和便是非常常見的,而且正如上面所示的幾個題一樣,一次預處理出全部的答案也是很常見的,要注意思考每個部分的聯系。
2、算法優化:
其實不知道該怎么給這個技巧取名字,算法優化涵蓋的范圍很廣,優化的地方可以有很多,所以總結也不可能全面,就針對自己遇到的一些題來總結下吧,像上面的預處理技巧其實也算是算法優化的一部分,只不過覺得很重要,所以特別分開了。
分為3個部分:
1、減少冗余計算。2、減少計算量。3、優化計算過程。
(1)、減少冗余計算
1、Codeforces659F-Polycarp and Hay
點擊打開鏈接
題意:給定一個矩陣,每個格子有一個數字,操作是改變格子中的數字,使其變小,操作后的矩陣必須滿足以下要求:所有格子中的數字和為k,非零格子中的數字要相同,至少一個格子要保留為原來的數字不變化,所有格子必須聯通,聯通是指所有非零格子都可以通過非零格子到達。
分析:很容易想到搜索來解決。剛開始的思路是,對于k的某一個約數d,如果格子中存在數字d且大于等于d的數字的個數如果大于等于k/di個的話,那么開始進行搜索并判斷是否滿足條件,滿足條件就把上一次走過的的格子全部變為d,其他全部變為0即可。具體是從正反開始枚舉k的每一個因子然后重復上述過程,但各種優化之后最終還是超時了,跑到了第57個樣例,發現方法選取有問題,首先需要枚舉每一個可行的因子,每次枚舉都需要遍歷所有格子,這樣時間復雜度過高。
其實可以換種方式:直接一次遍歷原圖即可,對于格子中的某一數字d,如果它是k的因子的話,那么直接從此開始搜索,判斷是否滿足條件,這樣優化之后復雜度明顯改善,但最后還是超時了,到了第95個test,但是這只是一個最直接的處理,算法的優化是無盡的,很重要的一點就是減少冗余,這樣便除去了大量的重復過程,如果每個處理的過程不是常數時間的話,那么這個優化對于時間復雜度的改善是明顯的!!!回到此題,明顯對于某一個數字d,如果搜索完所有可以到達的格子仍然后不符合條件的話,那么這個過程所有與d相等的格子無需在進行搜索處理,標記即可,因為這些格子也必定不符合條件。
?
#include <cmath> #include <cstring> #include <algorithm> #include <cstdio> using namespace std;typedef long long LL; const int N = 1010; LL k, di, tab[N][N]; int n, m, flag = 1, tot, cnt, sum; int vis[N][N], v[N][N]; int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};void DFS(int x, int y) {if (tab[x][y] == di) v[x][y] = 1;//標記數字相等的格子vis[x][y] = sum;if (++cnt == tot) { flag = 0; return ; }if (flag) {for (int i = 0; i < 4 && flag; i++) {int nx = x + dx[i], ny = y + dy[i];if (nx < 1 || nx > n || ny < 1 || ny > m || vis[nx][ny] == sum) continue;if (tab[nx][ny] >= di) DFS(nx, ny);}} }int main() {scanf("%d %d %I64d", &n, &m, &k);for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++) scanf("%I64d", &tab[i][j]);LL t = k/n/m;for (int i = 1; i <= n && flag; i++) {for (int j = 1; j <= m && flag; j++) {di = tab[i][j];if (k % di == 0 && di >= t && !v[i][j]) {cnt = 0; sum++;tot = k/tab[i][j];DFS(i, j);}}}if (flag) puts("NO");else {puts("YES");for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) printf("%I64d ", vis[i][j] == sum ? di : 0);puts("");}}return 0; }2、HDU5510-Bazinga
點擊打開鏈接
題意:給定n個字符串,分別s1、s2…..sn,找出最大的i,使得存在一個j(1<=j<i)使得sj不是si的子串。
分析:題目數據量很大,最多500個字符串,而且長度可以達到2000,然后最后50個樣例,估算下可知就算是使用kmp算法,直接暴力也必定超時,但是這種題沒有什么其他的高效解法,只能暴力,那么我們需要從優化算法的角度入手。其實只要稍加優化就能過了。
如果逆著枚舉所有i的話,那么無法優化,最壞情況下還是會枚舉完所有的i,肯定也會超時,順著枚舉i然后減少重復的判斷就可以了??梢灾?#xff0c;如果一個串i是另一個串j的子串的話,那么在枚舉時對于串i便無需在進行匹配了,只需要與j匹配即可,這題出題人肯定也是考察這一點,想想看,對于串的匹配,通常的算法是o(n^2),對于串i,如果前面的串中很多都是i的子串的如果不用判斷的話會減少大量的時間消耗大的重復判斷過程。
?不過值得一提的是使用strstr,或者search居然會比kmp快了近一倍,雖說一般這兩個函數是平方的,但在實際應用中一般比kmp快,但是這種大量的匹配過程為何還會快且快這么多呢?
#include <cstdio> #include <cstring> #include <algorithm> using namespace std;char s[510][2010]; int main() {int t, k = 0;int vis[510];scanf("%d", &t);while (t--) {int n;scanf("%d", &n);for (int i = 1; i <= n; i++) scanf("%s", s[i]);int ans = -1;memset(vis, 0, sizeof(vis));for (int i = 2; i <= n; i++) {for (int j = 1; j < i; j++) {if (!vis[j]) {int leni = strlen(s[i]);if (search(s[i], s[i]+leni, s[j], s[j]+strlen(s[j])) == s[i]+leni) { ans = i; break; }else vis[j] = 1;}}}printf("Case #%d: %d\n", ++k, ans);}return 0; }
(2)、減少計算量
1、uva10976- Fractions Again?!
點擊打開鏈接
題意:給定正整數k,找到所有的正整數x>=y使得1/k=1/x+1/y,并且輸出所有的式子。
分析:暴力枚舉即可,但是枚舉量多大呢?如果枚舉x,推導可知最優也要從k*(k+1)枚舉到2*k,太大了,明顯不可取,但是如果從枚舉y出發,可以知道y<=2*k,枚舉量大大減小,效率足夠高了。
#include <cstdio> #include <algorithm> #include <cstring> #include <utility> using namespace std;typedef pair<int, int> p; p ans[10000];int main() {int n;while (~scanf("%d", &n)){int k = 0;for (int i = 2*n; i >= n+1; i--){int j = n*i/(i-n);if (j*(i-n) == n*i && j >= 2*n && j <= n*(n+1)) ans[k++] = p(j, i);}printf("%d\n", k);for (int i = k-1; i >= 0; i--) printf("1/%d = 1/%d + 1/%d\n", n, ans[i].first, ans[i].second);}return 0; }?
2、Hdu5128 - The E-pangPalace
點擊打開鏈接
分析:最容易想到的就是預處理出所有的矩形,假設有n個矩形,之后n*n跑一遍,但這樣可能達到C(30,4)*C(30,4)的復雜度,會超時。其實沒必要,類似于折半枚舉(雙向搜索)的思想,只要枚舉矩形的兩個對角點即兩個點就行了,然后判斷另外兩個點是否存在即可。所以枚舉量最多只有C(30,4)了。而且判斷兩個矩形是否相交的話我們不需要分類出所有的情況,從反面考慮,如果一個矩形在另一個矩形的上方或者左方或者下方或者右方的話就說明不相交了。而且另外一點是,對于一個矩形,我們只需要枚舉一次對角頂點的組合,另外一個組合沒必要枚舉,這樣減少了很多不必要的枚舉。而且對于選出的4個點,按照x從左從左至右排列假設為a,b,c,d四個點的話,只有3種組合方式(a,b)與(c,d),(a,c)與(b,d),(a,d)與(b,c),全部枚舉一下就好了。為方便判斷,可以事先給坐標排序,之后枚舉所有大小為4的子集,并且每次對3種組合方式判斷一下取最大值。
這題的一個坑點:一個矩形可能完全包含在另一個矩形中,這種情況下面積取較大者,否則取和。
暴力枚舉不失為一種好辦法,但是過多的枚舉必然會導致效率低下了。所以下手之前可以分析下,看是否可以減少重復的沒必要的枚舉,而且有時候減少這些枚舉還可以保證正確率,就像這題如果枚舉矩形的兩個對角的組合的話可能會導致判斷不全面導致計算錯誤,之前做的時候就是這樣,發現答案變大了,原因是因為考慮不全面,但是其實是沒必要的。而且折半枚舉也是一種重要的思想,因為很多情況下只需要枚舉一部分量,然后另一部分量直接判斷或者進行查找就好了。?
?
(3)、優化計算過程
在此舉一個數據結構進行算法的優化的例子:1、codeforces675E ?Trains and Statistic 點擊打開鏈接
?題意:一個城市有n個車站,編號從1到n,在第i號車站只能買到第i+1號到ai號車站的票(ai>=i+1),令P(i,j)為從第i號車站去第j號車站所需要買的最少的票的數量,給定ai,求所有的P(i,j)之和(1<=i<j<=n).
分析:看起來像圖論題,但是無法存儲,考慮其他方法。無后效性,明顯的動態規劃,令dp[i]為從i號車站去之后i+1到n這些車站的總票數最小數量,可以知道需要逆推,狀態轉移即為dp[i] = dp[pos]+n-i-(a[i]-pos),pos為i+1到a[i]這些車站中a[pos]最大的一個編號,知道了狀態轉移還不行,因為需要有大量計算區間最大值的過程,使用線段樹優化即可。
#include <cstdio> #include <algorithm> #include <cstring> using namespace std;typedef long long LL; const int N = 100010; int id[N<<2], a[N]; LL dp[N], ans;void update(int root) {if (a[id[root<<1]] > a[id[root<<1|1]]) id[root] = id[root<<1];else id[root] = id[root<<1|1]; } void build(int root, int l, int r) {if (l == r) { id[root] = l; return ; }int t = (r-l) / 2;build(root<<1, l, l + t);build(root<<1|1, l + t+1, r);update(root); } int query(int l, int r, int L, int R, int root) {if (l > R || r < L) return -1;if (l <= L && R <= r) return id[root];int idls, idrs, t = (R-L) / 2;idls = query(l, r, L, L + t, root<<1);idrs = query(l, r, L + t+1, R, root<<1|1);if (idls == -1) return idrs;if (idrs == -1) return idls;if (a[idls] > a[idrs]) return idls;return idrs; } int main() {int n;scanf("%d", &n);for (int i = 1; i < n; i++) scanf("%d", a+i);a[n] = n; ans = dp[n-1] = 1;build(1, 1, n);for (int i = n-2; i >= 1; i--) {int pos = query(i+1, a[i], 1, n, 1);dp[i] = dp[pos] + n-i - a[i]-pos;ans += dp[i];}printf("%I64d\n", ans);return 0; }
算法優化的總結:
程序時間復雜度過高,除了換更優的算法之外,優化當前的算法也是很重要的。就像上面說明的那樣:減少當前計算過程中的冗余計算,比如說標記,刪除、從減少計算量出發優化,比如說部分枚舉,折半枚舉、或者優化某個 計算的過程,比如說數據結構優化。算法優化思想或者技巧需要知識的積累和對算法過程進行的仔細分析,難以總結全面。要想熟練運用,還得不斷地鍛煉和總結。
3、 STL的高效運用:
不管你是一個ACM競賽者還是一個開發程序員,STL都會是一個非常好的工具。如果能夠充分地利用STL,可以在代碼空間、執行時間和編碼效率上獲得極大的好處,代碼不僅高效簡潔而且看起來特別舒服。
首先粗略介紹下STL:
STL 大致可以分為三大類:算法(algorithm)、容器(Container)、迭代器(iterator)。
STL 容器是一些模板類,提供了多種組織數據的常用方法,例如vector(向量,類似于數組)、list(列表,類似于鏈表)、deque(雙向隊列)、set(集合)、map(映射)、stack(棧)queue(隊列)、priority_queue(優先隊列)等,
通過模板的參數我們可以指定容器中的元素類型。
STL 算法是一些模板函數,提供了相當多的有用算法和操作,從簡單如for_each(遍歷)到復雜如stable_sort(穩定排序)。STL 迭代器是對C 中的指針的一般化,用來將算法和容器聯系起來。幾乎所有的STL 算法都是通過迭代器來存取元素序列進行工作的,而STL 中的每一個容器也都定義了其本身所專有的迭代器,用以存取容器中的元素。有趣的是,普通的指針也可以像迭代器一樣工作。熟悉了STL 后,你會發現,很多功能只需要用短短的幾行就可以實現了。通過STL,我們可以構造出優雅而且高效的代碼,甚至比你自己手工實現的代碼效果還要好。
STL 的另外一個特點是,它是以源碼方式免費提供的,程序員不僅可以自由地使用這些代碼,也可以學習其源碼,甚至按照自己的需要去修改它。
在這里首先例舉ACM經常需要用到的STL容器或算法:
STl容器或適配器等:pair、list、vector、priority_queue、set、map、stack
常用STL 算法庫:sort快速排序算法、二分查找算法、枚舉排列算法 .
在這里只介紹下應用,如果想知道這個容器的具體操作可以去看書或者找度娘,這里不詳細介紹操作了.
①、容器或適配器部分:
1、? pair
相當于一個Struct,訪問方式舉個例子:pair<int,int> p; 那么第一個成員便是p.first、第二個p.second,pair使用起來很方便,簡單快速高效,可以當做一個二元struct使用,而且它定義了比較的方法,先根據第一個成員比較,在根據第二個,所以如果你的比較運算符是這樣,那么你就不需要定義比較函數了,而struct是不能直接進行比較的,構造pair的方法:make_pair。
下面是一個簡單的例子:
#include <cstdio> #include <algorithm> #include <cstring> #include <utility> #include <functional> using namespace std;const int N = 1010; typedef pair<int, int> p; p a[N];int main() {int k = 0;a[k++] = p(3, 4);a[k++] = p(3, 100);a[k++] = p(1, 2);a[k++] = p(4, 10);//首先按第一個成員從大到小排列,當第一個成員相等時,按第二個成員second從大到小排列sort(a, a+k, greater<p>());for (int i = 0; i < k; i++) printf("%d %d\n", a[i].first, a[i].second);return 0; }2、List
list是一個循環鏈表,其實我并不經常使用。關鍵在于這個容器的特點:快速插入和刪除。作用和vector差不多,但內部是用鏈表實現。
這個容器不支持隨機訪問,你不能[]或者利用通用算法操作,比如說要排序的話你只能利用成員函數比如list.sort(),而且很重要的一點,list的size()函數是線性的,因為是以遍歷函數distance實現的。
下面的這個題便可以用list的O(1)插入和刪除特性暴力解決,簡直是神技般的存在!
Hdu5127-Dogs' Candies
點擊打開鏈接
題意:一個以兩個數字p,q作為單個元素的序列,支持三種操作:
1、 從序列中去掉包含p,q的這兩個數字的元素。
2、向序列中添加包含p,q的這兩個數字的元素。
3、對于給定的x和y,找到一對p,q,使得px+qy最大,輸出這個最大值。
?
分析:這題雖然數據量比較大,但是卻可以暴力解決,據說現場賽時只有10個隊伍過了此題,正解很麻煩,cdq分治+凸包,然而我全都不會…..首先抓住這題的一個特性,題目可能存在大量的插入和刪除操作,而且對于尋找最大值的操作,只能循環遍歷一次,不存在貪心的選取策略能使得枚舉量減少。如果采取不刪除,而直接進行標記的策略便會超時,因為這樣會使得遍歷的時間增加。
根據以上的分析,抓住題目的特征,選取合適的方法,選用list作為容器來存儲。運行時間不到時限的一半,簡直神奇,如果選取其他的容器也都會超時。也許這就是傳說中的暴力姿勢比較好吧….
#include <cstdio> #include <algorithm> #include <cstring> #include <list> #include <utility> using namespace std;typedef long long LL; typedef pair<LL, LL> p; list<p> l;int main() {int n;while (scanf("%d", &n), n) {l.clear();for (int i = 0; i < n; i++) {LL a, b;int t;scanf("%d %I64d %I64d", &t, &a, &b);if (t == 1) l.push_back(p(a, b));else if (t == -1) l.erase(find(l.begin(), l.end(), p(a, b)));else {list<p>::iterator i = l.begin();LL ans = i->first * a + i->second * b;for (++i; i != l.end(); i++) ans = max(ans, i->first * a + i->second * b);printf("%I64d\n", ans);}}}return 0; }3、vector
可以說這是一個STL的神器,相當于一個不定長數組,利用這個你可以添加任意多的元素,容器以連續數組的方式存儲元素序列,可以將vector 看作是以順序結構實現的線性表。當我們在程序中需要使用動態數組時,vector將是一個理想的選擇。這個完全相當于把一個數組當成一個元素來進行使用了,可以直接相等,賦值操作等。
在這里介紹幾個比較經典的使用:
1、利用vector防止遞歸爆棧。2、利用vector來實現鄰接表(通常是vector<int>G[N],但這種方式可能會因為存儲量巨大然后vector的容量擴展導致TLE)3、利用vector存儲空間占用比較大的矩陣。
vector高效的原因在于配置了比其所容納的元素更多的內存,但其內存重新配置會花很多時間,(在這里順便說下,像new和malloc等函數要慎用,這屬于用時間開銷代價換取空間的舉措,非常容易TLE),vector確實非常好用,合理使用實則幫助很大。
Vector算是應用得最多的一個stl容器了,例子太多了,在此就不舉了.....
?
4、priority_queue:
這個也可以說是一個神器,優先隊列其實就是堆,在優先隊列中,元素被賦予優先級。當訪問元素時,具有最高優先級的元素最先被刪除。優先隊列具有最高級先出(first in, largest out)的行為特征。在這里說下這個怎么進行重載的問題。
兩種方式:
一、直接在struct或者class內部定義,那么你定義時直接指明元素類型就好了,因為你是定義了比較函數的。二、定義比較結構。
//內部定義: struct node{int x, y;node(int x = 0, int y = 0) : x(x), y(y) {}bool operator < (const node &a) const { return x > a.x; } }; priority_queue<node> q; //定義比較結構 struct node{int x, y;node(int x = 0, int y = 0) : x(x), y(y) {} }; struct cmp {bool operator () (const node &a, const node &b) { return a.x< b.x; } }; priority_queue<node, vector<node>,cmp> q;具體點就是可以用來貪心或者加速搜索,下面是對應的兩個題:
(1)、貪心
優先隊列最常用的就是貪心,舉個例子。
poj2010-Moo University - Financial Aid ? ??
?點擊打開鏈接
題意:有c只奶牛,現在要招募n只進動物學校,第i只奶牛的分數為si,需要的資金幫助為ai,現在有總共為f的資金,問招募到的n只奶牛中中位數最大是多少?
分析:采取貪心策略,盡量選擇大的,首先按分數從小到大排個序,之后從大到小枚舉中位數,所以我們要知道當前分數作為中位數下,需要的資金總和最小是多少。令l[i]表示選取第i+1號奶牛作為中位數時分數比它小的n/2頭奶牛需要的資金幫助的最小值,令r[i]表示選取第i-1號奶牛作為中位數時分數比它大的n/2頭奶牛需要的資金幫助的最小值。遍歷時用優先隊列存儲當前的分數,貪心地換掉需要的資金多的奶牛,一次預處理r[i]和l[i]即可,總的時間復雜度為O(nlogn)
#include <cstdio> #include <algorithm> #include <cstring> #include <queue> using namespace std;const int N = 100010; int l[N], r[N]; struct calf {int s, a; }ca[N]; bool cmp(calf x, calf y) { return x.s < y.s; }int main() {int n, c, f;scanf("%d %d %d", &n, &c, &f);for (int i = 1; i <= c; i++) scanf("%d %d", &ca[i].s, &ca[i].a);sort(ca+1, ca+1+c, cmp);n >>= 1;priority_queue <int> q;int sum = 0;for (int i = 1; i <= n; i++) q.push(ca[i].a), sum += ca[i].a;l[n] = sum;for (int i = n+1; i <= c-n-1; i++) {if (ca[i].a >= q.top()) l[i] = sum;else {sum -= q.top() - ca[i].a;q.pop(); q.push(ca[i].a);l[i] = sum;}}sum = 0;while (!q.empty()) q.pop();for (int i = c; i >= c-n+1; i--) q.push(ca[i].a), sum += ca[i].a;r[c-n+1] = sum;for (int i = c-n; i >= n+2; i--) {if (ca[i].a >= q.top()) r[i] = sum;else {sum -= q.top() - ca[i].a;q.pop(); q.push(ca[i].a);r[i] = sum;}}int ans = -1;for (int i = c-n; i >= n+1; i--) {if (r[i+1] + l[i-1] + ca[i].a <= f) {ans = ca[i].s;break;}}printf("%d\n", ans);return 0; }(2)、加速搜索
csu1780 -?簡單的圖論問題
點擊打開鏈接
這題很明顯屬于搜索題,bfs廣搜即可,但是需要找到權值最小的路徑的話,那么我們需要使用優先隊列來加速搜索和求解答案。每一步優先選擇權值小的結點來拓展狀態,且結點中記錄上一次擴展狀態時的方向,而且給一個點標記去重的需要考慮四個方向的狀態。
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <utility> #include <queue> #define INF 0x3f3f3f3f #define LL long long using namespace std; struct po{ int x, y, w, di; bool operator > (const po &a)const {return w > a.w;} }; int n, m, vis[505][505], v[505][505][4]; int maze[510][510], r1, c1, r2, c2, t; char st[5]; int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1}; int bfs() { priority_queue <po, vector<po>, greater<po> > q; q.push((po){r1, c1, maze[r1][c1], 0}); memset(vis, 0, sizeof(vis)); vis[r1][c1] = 1; while (!q.empty()){ po t = q.top(); q.pop(); if (t.x==r2 && t.y==c2) return t.w; for (int i = 0; i < 4; i++){ int nx = t.x+dx[i], ny = t.y+dy[i]; if (nx<1 || nx>n || ny<1 || ny>m || vis[nx][ny] || maze[nx][ny]==-1) continue; q.push((po){nx, ny, t.w+maze[nx][ny], 0}); vis[nx][ny] = 1; } } return -1; } int bfs1() { memset(v, 0, sizeof(v)); priority_queue <po, vector<po>, greater<po> > q; q.push((po){r1, c1, maze[r1][c1], -1}); v[r1][c1][0] = v[r1][c1][1] = v[r1][c1][2] = v[r1][c1][3] = 1; while(!q.empty()){ po t = q.top(); q.pop(); if (t.x==r2 && t.y==c2) return t.w; for(int i = 0;i < 4;i ++){ if(i == t.di) continue; int nx = t.x+dx[i], ny = t.y+dy[i]; if(nx<1 || nx>n || ny<1 || ny>m || v[nx][ny][i] || maze[nx][ny]==-1) continue; q.push((po){nx, ny, t.w+maze[nx][ny], i}); v[nx][ny][i] = 1; } } return -1; } int main() { while (~scanf("%d %d %d %d %d %d", &n, &m, &r1, &c1, &r2, &c2)){ memset(maze, -1, sizeof(maze)); for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j){ scanf("%s", st); if (st[0] != '*') sscanf(st, "%d", &maze[i][j]); } printf("Case %d: %d %d\n", ++t, bfs(), bfs1()); } return 0; }5、set:
set,顧名思義,集合,無重復元素,插入的元素自動按增序排列。
內部實現: 紅黑樹,一種平衡的二叉排序樹,其Compare 函數,類似于qsort 函數里的那個Compare 函數,作為紅黑樹在內部實現的比較方式。
在這里說下一些成員函數的時間復雜度:
insert() O(logn)、erase()O(logn)、find()O(logn)
lower_bound()O(logn) 查找第一個不小于k 的元素
upper_bound()O(logn) 查找第一個大于k 的元素
equal_range()O(logn) 以pair形式返回lower_bound和upper_bound,在需要確定一個增序序列中某一段都是某一值的情況下是非常有效的,這比通用算法更有效。
容器最主要的功能就是判重,也可以進行二分查找。
要允許重復元素,選用multiset即可。
容器性能:set與map的查找、刪除、插入性能都是對數復雜度。
沒有定義比較方式的元素需要進行重載,重載方式和優先隊列一樣。
判重是set的一個最常見的使用。
Uva11572- Unique Snowflakes
點擊打開鏈接
題意:給定一個長度為n序列A,找到一個最長的連續子序列,使得這個序列中沒有相同的元素。
分析:經典的O(n)尺取法,實現過程中需要判斷當前的右端點元素是否存在,利用set判斷即可。s.find(x) != s.end()和s.count(x)都可以。
#include <cstdio> #include <algorithm> #include <cstring> #include <set>using namespace std; int a[1000001]; set<int> s;int main() {int t, n;scanf("%d", &t);while (t--) {scanf("%d", &n);for (int i = 0; i < n; i++) scanf("%d", a+i);s.clear();int st = 0, en = 0, ma = 0;while (en < n) {while (en < n && !s.count(a[en])) s.insert(a[en++]);ma = max(ma, en-st);s.erase(a[st++]);}printf("%d\n", ma);}return 0; }6、map
這個容器也非常好,和set差不多,pair 組成的紅黑樹(所以比較方式和pair一樣),成員函數復雜度和前者一樣,在這里就不贅述了,容器適用于那些需要進行映射(可以根據Key找到對應的Value,作為hash還是不錯的),因此map是關聯數組。
要允許重復元素,使用multimap。
在這里指出:在C++11里,有unoder和hash的map和set,里面的元素沒有順序.速度比普通的set和map快了很多,但是需要自己重載hash
map最常見的應用就是進行映射。
hdu4460 -?Friend Chains
點擊打開鏈接
題意:給定n個人的名字和這n個人之間的朋友關系,間接相連也算是朋友,如果兩個人之間通過n個人相連,那么這條朋友鏈的長度即為n-1.求一個最小的k,使得對任意兩個人,他們之間的朋友鏈的長度不會超過k。
分析:明顯是求任意兩點之間的最短路,把那個最大的距離找到即可,但是給定的是名字,需要進行映射,用map映射為標號即可。而且這題的邊的權值為1,不需要用優先隊列,用普通隊列即可。值得注意的是,不需要進行n次bfs求取最短路,只需要進行兩次。首先選取一個點x進行最短路的求解,之后找到離x距離最大的那個點y在進行一次最短路的求解即可。因為第一次最短路肯定會求得整個圖中離圖的重心點最遠的那個點,也就是說處于整個圖的最端點。那么之后從這個點出發求得的最短路距離必定是整個圖中任意兩點之間距離的最大值。
#include <cstdio> #include <cstring> #include <string> #include <vector> #include <algorithm> #include <map> #include <queue> using namespace std;const int N = 1010; int vis[N], d[N]; map <string, int> mp; vector<int> G[N];int solve(int x, int n) {int ma = 0, res, cnt = 1;queue<int> q; q.push(x);memset(vis+1, 0, sizeof(int) * (n+1));memset(d+1, 0, sizeof(int) * (n+1));vis[x] = 1;while (!q.empty()) {int t = q.front(); q.pop();for (int i = 0; i < G[t].size(); i++) {int y = G[t][i];if (vis[y]) continue;vis[y] = 1;d[y] = d[t] + 1;if (ma < d[y]) res = y, ma = d[y];q.push(y); cnt++;}}return cnt != n ? -1 : x == 1 ? res: ma; }int main() {int n;while (scanf("%d", &n), n) {mp.clear();for (int i = 1; i <= n; i++) G[i].clear();char s[15], str[15];for (int i = 1; i <= n; i++) scanf("%s", s), mp[s] = i;int m;scanf("%d", &m);for (int i = 1; i <= m; i++) {scanf("%s %s", s, str);G[mp[s]].push_back(mp[str]);G[mp[str]].push_back(mp[s]);}int ans = solve(1, n);ans == -1 ? puts("-1") : printf("%d\n", solve(ans, n));}return 0; }7、stack:
stack,棧在STL里面它屬于容器適配器,對容器的重新包裝,后進先出結構。
經典使用:單調棧的實現。
poj2559 -?Largest Rectangle in a Histogram
點擊打開鏈接
題意:給定一個柱狀圖,由n個寬為1,長為hi的矩形從左至右依次排列組成,求里面包含的長方形的最大面積是多少?
分析:如果預處理一次區間最小值,暴力也要O(n^2),無法承受,換一種方式,我們可以枚舉每個h[i],求以當前高度作為長度的矩形的最大面積,所以需要做的就是對于固定的h[i],求出左端點L和右端點R,必定有h[L-1]<h[i]和h[R]<h[i],否則總可以更新左右端點,令l[i]為(j<=i且h[j-1]<h[i]的最大的j),r[i]為(j>i且h[j]<h[i]的最小的j). 怎么計算l[i]和r[i]. 思考下就可以知道可以利用棧解決。
考慮計算l[i]的情況,棧中維護的是高度依次減小的標號,也就是如果棧中從上到下的值依次為i,那么i>i+1且h[i]>h[i+1]。對于當前的高度h[i],如果有棧頂的元素h[k]>=h[i],證明左端點可以一直往左移動,不斷地取出棧頂元素,如果棧為空了證明可以移動到最左邊,也就是l[i]=0,否則h[i]=j+1。之所以利用棧求解,是因為每次都要取得左邊第一個高度小的端點和右邊第一個高度小的端點。這樣符合后進先出的性質,模擬下棧的工作情況便可以明白了。正反兩次預處理就可以計算出l和r了,之后遍歷一次高度求取最大值。
這樣的棧稱為單調棧,和單調隊列是類似的。
#include <cstdio> #include <algorithm> #include <cstring> #include <stack> #include <cctype> using namespace std;typedef long long LL; const int N = 100010; stack<int> s; template <class T> inline void read(T &res) {char c; res = 0;while (!isdigit(c = getchar()));while (isdigit(c)) res = res * 10 + c - '0', c = getchar(); } int l[N], r[N]; LL h[N];int main() {int n;while (read(n), n) {for (int i = 0; i < n; i++) read(h[i]);while (!s.empty()) s.pop();for (int i = 0; i < n; i++) {while (!s.empty() && h[s.top()] >= h[i]) s.pop();l[i] = s.empty() ? 0 : s.top()+1;s.push(i);}while (!s.empty()) s.pop();for (int i = n-1; i >= 0; i--) {while (!s.empty() && h[s.top()] >= h[i]) s.pop();r[i] = s.empty() ? n : s.top();s.push(i);}LL ans = 0;for (int i = 0; i < n; i++) ans = max(ans, (LL)h[i] * (r[i] - l[i]));printf("%I64d\n", ans);}return 0; }②? 、算法庫
主要的頭文件就是#include <algorithm>
1、 sort排序系列:
sort:對給定區間所有元素進行排序(全排)
stable_sort :對給定區間所有元素進行穩定排序,就是相等的元素位置不變,原來在前面的還在前面。
partial_sort :對給定區間所有元素部分排序,就是找出你指定的數目最小或最大的值放在最前面或最后面,比如說我只要找到1000000個數中最大的五個數,那你用這個函數是最好的,排序后最大的五個數就在最前面的五個位置,其他的元素位置分布不確定。
partial_sort_copy:對給定區間復制并排序,和上面的一樣,只是這是指定區間進行復制然后排序的。
nth_element :找出給定區間的某個位置對應的元素,根據比較函數找到第n個最大(小)元素,適用于尋找“第n個元素”。
is_sorted :判斷一個區間是否已經排好序(返回bool值判斷是否已排序)
partition :使得符合某個條件的元素放在前面,劃分區間函數,將 [first, last)中所有滿足的元素置于不滿足的元素前面,這個函數會返回迭代器,設返回的迭代器為 i,則對 [first, i)中的任意迭代器 j,*j滿足給定的判斷,對 [i, last) 中的任意迭代器 k,*k不滿足。
stable_partition :相對穩定的使得符合某個條件的元素放在前面(和上面的一樣,只是位置不變)
這個函數真是絕對的神助,相比qsort沒這么麻煩了。使用時根據需要選擇合理的排序函數即可,所有的排序函數默認從小到大排序,可以定義自己的比較方式。
sort排序十分常見,在此舉個其他排序的例子:
xtu1050-第K個數
Description
給你一個整數序列和若干個問題,問題是這個序列的第i個元素到第j個元素的片斷中的第k大數是什么?比如說給你的序列為(1, 5, 2, 6, 3, 7, 4),問題是(2,5,3),則從第2個元素到第5個元素為(5,2,6,3),排序以后是(2,3,5,6),則第三個數是5。
輸入:
第一行為兩個整數n,m(1 <= n <= 100 000, 1 <= m <= 5 000),表示序列的元素個數和問題的個數,第二行為n個正整數的序列,每個整數為一個32位整數,兩個數之間有一個空格隔開。以后m行為問題,每個問題由三個整數表示i,j,k,第i個元素到第j個元素的片斷中的第k大數是什么?(元素序號從1開始計數,i<=j,1<=k<=j-i+1)
輸出:
每行輸出對應問題的結果。
Sample Input
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
Sample Output
5
6
3
分析:只需要選擇第k個數,直接nth_element,線性復雜度暴力水過………而且這題解法很多,比如用線段樹等數據結構進行求解會是更優的。
#include <cstdio> #include <algorithm>using namespace std; int a[100010]; int b[100010];int main() {int n, t, i, j, k;scanf("%d %d", &n, &t);for (i = 1; i <= n; i++) scanf("%d", &a[i]);while (t--){copy(a+1, a+n+1, b+1);scanf("%d %d %d", &i, &j, &k);nth_element(b+i, b+i+k-1, b+j+1);printf("%d\n", b[i+k-1]);}return 0; }2、 二分系列:
二分檢索,復雜度O(log(last-first))
itr =upper_bound(first, last, value, cmp);
//itr 指向大于value 的第一個值(或容器末尾)
itr = lower_bound(first, last, value, cmp);
//itr 指向不小于valude 的第一個值(或容器末尾)
pairequal_range(first, last, value, cmp);
//找出等于value 的值的范圍O(2*log(last– first))
Binary_search(first,last, value)返回bool值,找到則true,否則false。
二分經常會與其他算法結合,二分的應用是也是十分常見的,在這舉個例子說明下找一個數或等于一個數的一個范圍的二分應用:
hdu1496-Equations
點擊打開鏈接
題意:給定a,b,c,d四個數,求有多少對x1,x2,x3,x4滿a*x1^2+b*x2^2+c*x3^2+d*x4^2=0
分析:暴力會超時,把ax1*x1+bx2*x2打表,利用二分查找,排序之后equal_range尋找等于這個值的范圍就行了。
#include <iostream> #include <algorithm> #include <cstring>using namespace std; int val[40010];int main() {pair <int*, int*> p;int a, b, c, d;while (cin >> a >> b >> c >> d) {if( (a > 0 && b > 0 && c > 0 && d > 0) || (a < 0 && b < 0 && c < 0 && d < 0)){cout << 0 << endl;continue;}memset(val, 0, sizeof(val));int k = 0;for (int i = -100; i <= 100; i++){if (i == 0) continue;for (int j = -100; j <= 100; j++) {if (j == 0) continue;val[k++] = a*i*i + b*j*j;}}sort(val, val+k);int cnt = 0;for (int j = -100; j <= 100; j++) {if (j == 0) continue;for (int i = -100; i <= 100; i++) {if (i == 0) continue;int sum = c*j*j + d*i*i;p = equal_range(val, val+k, -sum);cnt += p.second - p.first;}}cout << cnt << endl;}return 0; }利用二分查找時間復雜度降到了o(n*nlog(n))。當然這個題目可以hash,這是最好的辦法,O(1)查找,復雜度更小了。
3、 排列系列:
這兩個函數都可以用來枚舉字典序排列,那種水題直接用這個就解決了。
在這里貼個網址,看了下,說的挺好的;????????
http://blog.sina.com.cn/s/blog_9f7ea4390101101u.html
?
4、 常用函數:
最后補充下常用函數,//其實是我常用的函數...
copy函數,直接拷貝,比for循環高效,最壞為線性復雜度,而且這個可以說是一個copy族函數,還有類似的滿足一定條件的copy函數如copy_if等。
find、find_i:查找第一個匹配的值或第一個滿足函數使其為true的值位置,沒有返回指定區間的末尾,線性復雜度,還有一些不怎么常用的find族函數就不多介紹了。
count、count_if: 返回匹配或使函數為true的值的個數,線性復雜度。
search,這是尋找序列是否存在于另一個序列中的函數,挺好用的,某些簡單的尋找公共子串的題就可以這樣寫,時間復雜度二次。
reverse,翻轉一個區間的值,我經常遇到需要這種題,直接reverse了,不需要for循環了,主要是方便。
for_each:直接對一個區間內的每個元素執行后面的函數操作,寫起來簡單。
max、min、max_element、min_element:尋找兩個數或者一個區間的最大最小值,都可以添加比較函數參數。
集合操作函數:includes、set_union、set_difference、set_intersection、set_symmetric_difference、前面這些函數的最差復雜度為線性,另外附加一個序列的操作函數merge,相當于歸并排序中的合并函數,時間復雜度為線性,注意這些函數的操作對象都必須是升序的。
這里舉個例子:
#include<cstdio> #include<algorithm> using namespace std; void out(int a) { if (a != -1) printf("%d ",a); } int main() { int a[5] = {1, 8, 10, 52, 100}; int b[5] = {6, 8, 9, 10, 1000}; int c[20]; printf("a集合為:"); for_each(a, a+5, out); puts(""); printf("b集合為:"); for_each(b, b+5, out); puts(""); //判斷b是否是a的子集。 if(includes(a, a+5, b, b+5)) printf("bis a sub set of a\n"); //合并兩個有序序列,必須為合并后的序列分配空間,否則程序會崩潰。 printf("兩個集合的合并序列為:"); merge(a, a+5, b, b+5, c); for_each(c, c+10, out); puts(""); //求兩個集合的并集。 fill(c, c+20, -1); set_union(a, a+5, b, b+5, c); printf("兩個集合的并集為:"); for_each(c, c+10, out); puts(""); //求兩個集合的交集。 fill(c, c+20, -1); set_intersection(a, a+5, b, b+5, c); printf("兩個集合的交集為:"); for_each(c, c+10, out); puts(""); //求兩個集合的差集,這里為a-b。 fill(c, c+20, -1); set_difference(a, a+5, b, b+5, c); printf("a-b的差集為:"); for_each(c, c+10, out); puts(""); //求兩個集合的差集,這里為b-a。 fill(c, c+20, -1); set_difference(b, b+5, a, a+5, c); printf("b-a的差集為:"); for_each(c, c+10, out); puts(""); //求兩個集合的對稱差 set_symmetric_difference(a, a+5, b, b+5,c); printf("兩個集合的對稱差為:"); for_each(c, c+10, out); puts(""); return 0; }下面的這個題便說明了STL的方便與實用:
codeforces730A - Toda2
點擊打開鏈接
題意:有n個人,每個人有一個rating,目的是使得每個人的rating相同而且盡可能的大,每次可以選取2到5個人,使得每個人的rating減1,如果rating已經為0了,那么不減少,輸出你的操作過程的每一步,每一步為一個01序列,如果選擇了第i個人,那么序列中第i位為1。
分析:模擬題,不過比較麻煩,為方便起見,利用STL實現,思路是每次選取兩到三個人進行rating的減小,然后每次選取rating較大的幾個人減少就行了,如果r全部相等就可以了。
#include <cstdio> #include <algorithm> #include <cstring> #include <set> #include <vector> #include <string> #include <iostream> using namespace std;struct p{int r, id;p(int r = 0, int id = 0) : r(r), id(id) {}bool operator <(const p &x)const { return r > x.r; } }; vector<string> ans; multiset<p> s;int main() {ios::sync_with_stdio(0);int n;cin >> n;for (int i = 0; i < n; i++) {int r;cin >> r;s.insert(p(r, i));}while (s.begin()->r != s.rbegin()->r){string t(n, '0');int num = 2;if(s.count(*s.begin()) == 3) num++;vector<p> tmp;for (int i = 1; i <= num; i++) {p a = *s.begin();s.erase(s.begin());t[a.id] = '1';if (a.r) a.r--;tmp.push_back(a);}s.insert(tmp.begin(), tmp.end());ans.push_back(t);}cout << s.begin()->r << "\n" << ans.size() << "\n";for (int i = 0; i < ans.size(); i++) cout << ans[i] << "\n";return 0; }③? 、STL使用的總結:
STl不失為一種絕佳工具,合理的使用是一種很好的技巧,但是需要指出不要過分依賴于STL,STL里面函數確實多的你幾乎每個操作都有函數代替,寫起來又簡單。但凡是都有利弊,STl不好的一個地方就是如果碰到某些卡時間、卡空間、各種卡的題目,那些函數的效率問題就大打折扣了,一般手寫的效率會高。
1、STL的容器如queue還有很多函數都比較慢,在碰到那些數據量大的題目時效率確實不高,很容易超時,poj1426 BFS水題吧,用STl容器queue各種超時,一直想不明白,后面自己寫個queue,直接就32ms AC了,通過這個也更加體會到這一點(最致命的一點)
2、STL封裝了大量的數據結構和算法,使用起來一定需要注意,STl容器的操作都是建立在迭代器之上的,迭代器的使用也需要注意,不然各種問題,程序崩潰…
3、STL算法極其多,大致分為搜索、修改、排序、集合與工具等,每一種類中的算法都比較多,有助于我們快速地完成程序設計。
4、使用STL容器時注意使用自帶的成員函數要比通用算法高效很多。
5、STL的算法思想是非常值得借鑒的,你可以去查看以學習。
6、STL也是存在缺點的,沒有提供任何泛型的圖或樹結構,只能自己實現。
終于總結完了~
最后給自己一句話吧:Godhelp those who help themselves.
總結
以上是生活随笔為你收集整理的ACM解题的一些技巧和方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 向量和向量空间
- 下一篇: 40vf什么意思_LED 的基本术语VF