1.【編程題】消除重復元素
時間限制:1秒
空間限制:32768K
小易有一個長度為n序列,小易想移除掉里面的重復元素,但是小易想是對於每種元素保留最后出現的那個。小易遇到了困難,希望你來幫助他。
輸入描述:
輸入包括兩行:
第一行為序列長度n(1 ≤ n ≤ 50)
第二行為n個數sequence[i](1 ≤ sequence[i] ≤ 1000),以空格分隔
輸出描述:
輸出消除重復元素之后的序列,以空格分隔,行末無空格
輸入例子:
9
100 100 100 99 99 99 100 100 100
輸出例子:
99 100
題目分析:
1.解法一:這道題目里面序列長度並不長,最多不會超過50個,因此使用搜索的方式也可以,雖然效率不高,但是可以使用。參考其他人的代碼,對於去重有一種技巧,這道題目是從后向前搜索,因此在搜索時候,將重復的數字置位,這里因為數的范圍在1-1000,因此置位為0.(注意審題。)代碼如下:
#include<iostream>
using namespace std; int main() { int n; cin >> n; int c[50]; for (int i = 0; i < n; i++) cin >> c[i]; //倒序
for (int i = n - 1; i >= 0; i--) { int b = c[i]; for (int j = i - 1; j >= 0; j--) { if (c[j] - b == 0) c[j] = 0; } } //輸出
for (int i = 0; i < n; i++) { if (c[i] != 0) { cout << c[i]; if (i < n - 1) cout << ' '; } } return 0; }
2.解法2:這里涉及到一個數據存儲的問題,提煉出本質:就是說從后向前搜索,將數據存入數據結構中,首先判重復,檢查已經存下的數據中是否有該數據,如果重復就不加入數據結構中。
有人使用隊列結構:使用一個隊列,對於每次輸入,判斷該數是否已經存在,若存在,刪除。然后壓入隊列。
但更多得是使用set:set是一種關容器,是關鍵字的簡單集合,當只想知道一個值是否存在的時候,set是最有用的。
#include <iostream> #include <vector> #include <set>
using namespace std; int main() { vector<int> input, res; set<int> s; int n,x; //輸入
cin >> n; for (int i = 0; i < n; i++) { cin >> x; input.push_back(x); } //set 查重 從后向前查重
for (int i = input.size() - 1; i >= 0; i--) { if (s.find(input[i]) == s.end()) //s.end()
{ s.insert(input[i]); res.push_back(input[i]);//注意set會對元素順序根據關鍵字改變 因此需要存放到vector中 } } //倒序輸出 cout << res[res.size() - 1]; for (int i = res.size() - 2; i >= 0; i--) cout << " " << res[i]; return 0; }
2.【編程題】雙核處理
時間限制:1秒
空間限制:32768K
輸入描述:
輸入包括兩行:
第一行為整數n(1 ≤ n ≤ 50)
第二行為n個整數length[i](1024 ≤ length[i] ≤ 4194304),表示每個任務的長度為length[i]kb,每個數均為1024的倍數。
輸出描述:
輸出一個整數,表示最少需要處理的時間
輸入例子:
5
3072 3072 7168 3072 1024
輸出例子:9216
題目分析和解答:
1.解法1:這道題初拿到感覺到有點難下手,后來正常想,假如說所有任務需要的處理的時長的Task-sum,雙核的處理器的情況下,每一個處理器處理Task-sum/2的情況下就是最優的狀態,但是事實上單個任務的Task不可分割,所以應該盡量雙核工作的時長應該盡量逼近Task_sum/2。這里實際上就轉換為0-1背包問題。
這里的解法就是非常中規中矩的動態規划,在空間上有較大的可優化空間,具體見解法2.在做解題的時候有幾個注意點:
(1)數組的長度不定,背包(內核的處理時長Task_sum/2)容量也需要根據測試用例來確定,因此需要動態申請二維數組,也需要在最后動態釋放空間。申請的二維數組的長度也需要注意一下,邊界條件和填表時遞增的初始值也要注意。
(2)另外一個容易出錯點:我在做題的時候,輸入進來的數據用的是vector,下標是(0-n-1),但是在用的時候,下標用的是(1-n),因此一開始的時候通過率為90%,后來發現是我的下標弄錯的原因,所以做題目一定要仔細。
(3)最后一個注意時:就是最后的結果,實際上獲得的結果res是對於一個內核來說,最逼近(task_sum/2)的狀態,它不可能超過task_sum/2,因此所取的值一定是task_sum - res。
#include <iostream> #include <algorithm> #include <vector>
using namespace std; int main() { int n = 0; //任務總數;
int t; int sum=0,half =0; vector<int> task; //輸入
cin >> n; task.push_back(0); // task 的第一位
for (int i = 0; i < n; i++) { cin >> t; t = t/1024; sum += t; task.push_back(t); } half = sum / 2; // 申請二維數組
int **c = new int *[n+1]; //+1
for (int i = 0; i <=n; i++) c[i] = new int [half+1]; for (int i = 0; i <= n; i++) c[i][0] = 0; for (int j = 0; j <= half; j++) c[0][j] = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= half; j++) { if (task[i] > j) c[i][j] = c[i - 1][j]; else c[i][j] = max(c[i - 1][j - task[i]] + task[i], c[i - 1][j]); } } cout<<(sum-c[n][half])*1024; //刪除申請的二維數組
for(int i =0;i<=n;i++) delete []c[i]; delete []c; return 0; }
2.解法2:優化的動態規划,在空間上進行優化;
#include <iostream> #include <algorithm> #include <vector>
using namespace std; int main() { int n = 0; //任務總數;
int t; int sum =0,half =0; vector<int> task; //輸入
cin >> n; for (int i = 0; i < n; i++) { cin >> t; t = t/1024; sum += t; task.push_back(t); } half = sum / 2; int *dp = new int[half+1]; //+1
for(int i =0;i<=half;i++ ) dp[i]=0; for(int i =0;i<n;i++) //注意 for里面
{ for(int j = half;j>=task[i];j--) { dp[j] = max(dp[j],dp[j-task[i]]+task[i]); } } cout<<(sum-dp[half])*1024; delete []dp; return 0; }
3.[編程題] 趕去公司
時間限制:1秒
空間限制:32768K
輸入描述:
輸入數據包括五行:
第一行為周圍出租車打車點的個數n(1 ≤ n ≤ 50)
第二行為每個出租車打車點的橫坐標tX[i] (-10000 ≤ tX[i] ≤ 10000)
第三行為每個出租車打車點的縱坐標tY[i] (-10000 ≤ tY[i] ≤ 10000)
第四行為辦公室坐標gx,gy(-10000 ≤ gx,gy ≤ 10000),以空格分隔
第五行為走路時間walkTime(1 ≤ walkTime ≤ 1000)和taxiTime(1 ≤ taxiTime ≤ 1000),以空格分隔
輸出描述:
輸出一個整數表示,小易最快能趕到辦公室的時間
輸入例子:
2
-2 -2
0 -2
-4 -2
15 3
輸出例子:
42
題目分析和解答:
這一道題目比較簡單,共有兩種去到公司的方法,走路或者打車,題意提供的打車點不超過50個,也就是說在最多選擇的情況下不會超過51種方法,因此完全可以采用暴力枚舉的方法解決這個問題。程序如下。
#include<iostream> #include<cmath> #include<vector>
using namespace std; int main() { int n,gx,gy,wtime,ttime; vector<int> tx, ty; int min = 0, res = 0; cin >> n; for (int i = 0; i < n; i++) { int temp; cin >> temp; tx.push_back(temp); } for (int i = 0; i < n; i++) { int temp; cin >> temp; ty.push_back(temp); } cin >> gx >> gy >> wtime >> ttime; // walking value;
min = (abs(gx) + abs(gy)) * wtime; for (int i = 0; i < n; i++) { res = (abs(tx[i]) + abs(ty[i])) * wtime + (abs(gx - tx[i]) + abs(gy - ty[i])) *ttime; if (res < min) min = res; } cout << min; return 0; }
4.【編程題】 調整隊形
時間限制:1秒
空間限制:32768K
GGBBG -> GGBGB -> GGGBB
這樣就使之前的兩處男女相鄰變為一處相鄰,需要調整隊形2次
輸入描述:
輸入數據包括一個長度為n且只包含G和B的字符串.n不超過50.
輸出描述:
輸出一個整數,表示最少需要的調整隊伍的次數
輸入例子:
GGBBG
輸出例子:
2
題目分析和解答:
這道題目並不復雜,但是一開始想復雜了,看到最小調整隊伍的次數的時候,一開始就想到的是動態規划里“字符串相似度”問題解決,但是考慮了之后發現比較難發現最優子結構,然后重新審題,字符串長度最多不超過50,可以知道這不是一個復雜的問題,問題的復雜度也不會高到哪里去。
因此實際上交換的結果就是女生和女生在一起,男生和男生在一起,只有兩種可能:GG……GBB……B,或者BB……BGG……B,實際上最終的交換次數可以如下表計算:
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
|
G |
G |
B |
B |
G |
G |
B |
Swap |
B |
B |
B |
G |
G |
G |
G |
實際上Boy的交換次數計算為: (2-0)+(3-1)+(6-2) = 8次;
因此按照思路,計算兩種安排可能下的交換次數,取最小值即為答案。
#include <iostream> #include <string>
using namespace std; int main() { string list; int bnum = 0, gnum = 0; int b_index_sum = 0, g_index_sum = 0; int b_move = 0, g_move = 0; cin >> list; for (int i = 0; i < list.length(); i++) { if (list[i] == 'B')//boy if == 一開始寫錯= 啊低級錯誤
{ b_index_sum += i; bnum++; } if (list[i] == 'G')//girl
{ g_index_sum += i; gnum++; } } //boy-move-step
b_move = b_index_sum - (bnum - 1)*bnum / 2; //girl-move-step
g_move = g_index_sum - (gnum - 1)*gnum / 2; cout << ((b_move < g_move) ? b_move : g_move); //運算符的優先級 注意一下<< 優先級大於三目運算
return 0; }
5.【編程題】魔力手環
時間限制:1秒
空間限制:32768K
輸入描述:
輸入數據包括兩行:
第一行為兩個整數n(2 ≤ n ≤ 50)和k(1 ≤ k ≤ 2000000000),以空格分隔
第二行為魔力手環初始的n個數,以空格分隔。范圍都在0至99.
輸出描述:
輸出魔力手環使用k次之后的狀態,以空格分隔,行末無空格。
輸入例子:
3 2
1 2 3
輸出例子:
8 9 7
題目分析和解答:
這一道題目如果直接來求解,代碼很簡單,但是問題就出在 k(1 ≤ k ≤ 2000000000) 上,k的值太大,則很容易就超時超空間,參看了別人的解答,發現這道題目是一道蠻典型的矩陣快速冪模板題,矩陣快速冪用於求解這種大量遞推次數的題目,求解的關鍵也是獲得遞推矩陣。
在這道題目中,假設初始狀態 T0 = (x1 , x2 , x3…… xn),遞推矩陣為A,則第K次后狀態為Tk = AK * T0;
根據題意,遞推矩陣為如下類似所示,使用快速冪取模模板求解即可,注意矩陣中0比較多,這一點可以優化一下矩陣乘法。
#include <iostream> #include <cstdio> #include <cstring>
using namespace std; const int NUM = 51; const int MOD = 100; struct mat{ int n, m; //行,列
int data[NUM][NUM]; }; /* mat mul(mat A, mat B) //矩陣乘法 這題里需要考慮一下 { mat ret; ret.n = A.n; //行 ret.m = B.m; //列 for (int i = 0; i < A.n; i++) { for (int j = 0; j < B.m; j++) { ret.data[i][j] = 0; for (int k = 0; k < B.n; k++) //A.m = B.n ret.data[i][j] += (A.data[i][k] * B.data[k][j]); if (ret.data[i][j] >= MOD) //減少一定的取模運算 取模操作耗時 ret.data[i][j] %= MOD; } } return ret; }*/
// 優化代碼
mat mul(mat A, mat B) //矩陣乘法 這題里需要考慮一下
{ mat ret; ret.n = A.n; //行
ret.m = B.m; //列
memset(ret.data,0,sizeof(ret.data)); for (int i = 0; i < A.n; i++) { for (int k = 0; k < B.n; k++) { if(A.data[i][k]){ for (int j = 0; j < B.m; j++) { //A.m = B.n
ret.data[i][j] += (A.data[i][k] * B.data[k][j]); if (ret.data[i][j] >= MOD) ret.data[i][j] %= MOD; } } } } return ret; } mat mypow(mat A, long long n) { mat ans; ans.n = ans.m = A.n; if (n == 1) return A; //ans 初始化為單位矩陣
memset(ans.data, 0, sizeof(ans.data)); for (int i = 0; i < ans.n; i++) ans.data[i][i] = 1; if (n == 0) return ans; while (n) { if (n & 1) ans = mul(ans, A); A = mul(A, A); n >>= 1; } return ans; } int main() { int n; long long k; //k 次操作
mat base,org,ret; memset(org.data, 0, sizeof(org.data)); cin >> n >> k; //n個數 k次變化
for (int i = 0; i < n; i++) { int d; cin >> d; org.data[i][0] = d; } //org為源狀態向量
org.n = n; org.m = 1; //構建遞推矩陣;
base.n = base.m = n; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { if (i == j || (i + 1) % n == j) base.data[i][j] = 1; else
base.data[i][j] = 0; } base = mypow(base, k); ret = mul(base, org); for (int i = 0; i < n-1; i++) { cout << ret.data[i][0] << " "; } cout << ret.data[n-1][0]; return 0; }
時間限制:1秒
空間限制:32768K
輸入描述:
輸入數據有n+1行:
第一行為工程師人數n(1 ≤ n ≤ 6)
接下來的n行,每行一個字符串表示第i(1 ≤ i ≤ n)個人能夠勝任的工作(字符串不一定等長的)
輸出描述:
輸出一個整數,表示有多少種不同的工作安排方案
·
輸入例子:
6
012345
012345
012345
012345
012345
012345
輸出例子:
720
題目分析解析:
這一道題目看起來就比較適合回溯法。解題代碼如下所示:
#include<iostream> #include<string> #include<vector> #include<set>
using namespace std; vector<string> v; set<int> ret; int n; int tot =0; void track_back(int cur) { if (cur == n) tot++; //為一種安排
else { string curr = v[cur]; for (int i = 0; i < curr.length(); i++) { int s = curr[i] - '0'; if (ret.find(s) == ret.end()) { ret.insert(s); track_back(cur + 1); ret.erase(s); } } } } int main() { cin >> n; for (int i = 0; i < n; i++) { string temp; cin >> temp; v.push_back(temp); } track_back(0); cout << tot; return 0; }
7.【編程題】 集合
時間限制:1秒
空間限制:32768K
小易的老師給了小易這樣一個集合:
S = { p/q | w ≤ p ≤ x, y ≤ q ≤ z }
需要根據給定的w,x,y,z,求出集合中一共有多少個元素。小易才學習了集合還解決不了這個復雜的問題,需要你來幫助他。
輸入描述:
輸入包括一行:
一共4個整數分別是w(1 ≤ w ≤ x),x(1 ≤ x ≤ 100),y(1 ≤ y ≤ z),z(1 ≤ z ≤ 100).以空格分隔
輸出描述:
輸出集合中元素的個數
輸入例子:
1 10 1 1
輸出例子:
10
題目解析:
這一道題目很簡單,就是一個set判重的問題,但是注意一下分數與浮點數的問題。
看別人的建議說不能直接相除,因為浮點數不精確。但我用了double類型的代碼也AC了。所以覺得有點奇怪。還是要嘗試搞明白一點。
建議說建一個結構體來保存分數,主要思想如下所示。
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a%b); } int g = gcd(i, j); s.insert(make_pair(i/g, j/g));
#include<iostream> #include<set>
using namespace std; int main() { set<double> ret; int w, x, y, z; cin >> w >> x >> y >> z; //輸入
for (int i = w; i <= x; i++) for (int j = y; j <= z; j++) { double temp = (double)i / (double)j; if (ret.find(temp) == ret.end()) ret.insert(temp); } cout<<ret.size(); return 0; }
8.[編程題] 奇怪的表達式求值
時間限制:1秒
空間限制:32768K
輸入描述:
輸入為一行字符串,即一個表達式。其中運算符只有-,+,*。參與計算的數字只有0~9.
保證表達式都是合法的,排列規則如樣例所示。
輸出描述:
輸出一個數,即表達式的值
輸入例子:
3+5*7
輸出例子:
56
題目解析:
這道題目很簡單,邏輯理清楚即可,注意一下ASCII碼與整型之間的轉換。
#include<iostream> #include<string>
using namespace std; int main() { string s; int res; cin >> s; res = s[0]; int i = 0; while (i <= s.length() - 1){ if (s[i + 1] == '+') res = res + s[i + 2]; if (s[i + 1] == '-') res = res - s[i + 2]; if (s[i + 1] == '*') res = res * s[i + 2]; i += 2; } cout << res; return 0; }
時間限制:1秒
空間限制:32768K
輸入描述:
輸入數據包括n+1行:
第一行為一個整數n(1 ≤ n ≤ 50),即棋盤的大小
接下來的n行每行一個字符串表示第i行棋盤的顏色,'W'表示白色,'B'表示黑色
輸出描述:
輸出小易會塗畫的區域大小
輸入例子:
3
BWW
BBB
BWB
輸出例子:
3
題目解析:
這道題目題意有一點不清,但是本質上不難的題目,就是注意一下是連續的相同顏色的區域。
#include<iostream> #include<string> #include<vector> #include<algorithm>
using namespace std; int main() { int n; vector<string> s; cin >> n; for (int i = 0; i < n; i++) { string temp; cin >> temp; s.push_back(temp); } int max = 0; for (int j = 0; j < n; j++) { int count = 1; for (int i = 0; i < n-1; i++) { if (s[i][j] == s[i+1][j]) { count++; max = ((max > count) ? max : count); } else count = 1; } } cout << max; return 0; }
10.[編程題] 小易記單詞
時間限制:1秒
空間限制:32768K
輸入描述:
輸入數據包括三行:
第一行為兩個整數n(1 ≤ n ≤ 50)和m(1 ≤ m ≤ 50)。以空格分隔
第二行為n個字符串,表示小易能記住的單詞,以空格分隔,每個單詞的長度小於等於50。
第三行為m個字符串,系統提供的單詞,以空格分隔,每個單詞的長度小於等於50。
輸出描述:
輸出一個整數表示小易能獲得的分數
輸入例子:
3 4
apple orange strawberry
strawberry orange grapefruit watermelon
輸出例子:
136
題目解析:
這題也很簡單,就是一個去重的問題。
#include<iostream> #include<set> #include<vector> #include<string>
using namespace std; int main() { int n, m; set<string> sys; set<string> mem; int ret = 0; cin >> n >> m; for (int i = 0; i < n; i++) { string temp; cin >> temp; mem.insert(temp); } for (int i = 0; i < m; i++) { string temp; cin >> temp; sys.insert(temp); } for(set<string>::iterator it=mem.begin();it!=mem.end();it++) { if(sys.find(*it)!=sys.end()) ret += (it->length()) * (it->length()); } cout << ret; return 0; }
時間限制:1秒
空間限制:32768K
輸入描述:
輸入包括兩行:
第一行為整數n(1 ≤ n ≤ 50),即一共有n塊磚塊
第二行為n個整數,表示每一塊磚塊的高度height[i] (1 ≤ height[i] ≤ 500000)
輸出描述:
如果小易能堆砌出兩座高度相同的塔,輸出最高能拼湊的高度,如果不能則輸出-1. 保證答案不大於500000。
輸入例子:
3
2 3 5
輸出例子:
5
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int maxh = 500000 + 5; int a[55]; int dp[2][maxh]; int main() { int n; int sum = 0; cin >> n; for (int i = 1; i <=n; i++) { cin >> a[i]; sum += a[i]; //由題意得答案最大不超過500000??? } memset(dp[0], -1, sizeof(dp[0])); // why -1 dp[0][0] = 0; int t = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j <= sum; j++) { dp[t][j] = dp[t ^ 1][j]; //【1】 if ((j + a[i] <= sum) && (dp[t ^ 1][j + a[i]] >= 0)) dp[t][j] = max(dp[t][j], dp[t ^ 1][j + a[i]] + a[i]); if ((a[i] - j >= 0) && (dp[t ^ 1][a[i] - j] >= 0)) dp[t][j] = max(dp[t][j], dp[t ^ 1][a[i] - j] + a[i] - j); if (j - a[i] >= 0 && dp[t ^ 1][j - a[i]] >= 0) dp[t][j] = max(dp[t][j], dp[t ^ 1][j - a[i]]); } t ^= 1; } cout << (dp[t ^ 1][0] == 0 ? -1 : dp[t ^ 1][0]); return 0; }
12.[編程題] 分餅干
時間限制:1秒
空間限制:32768K
輸入描述:
輸入包括兩行:
第一行為盒子上的數值k,模糊的數位用X表示,長度小於18(可能有多個模糊的數位)
第二行為小朋友的人數n
輸出描述:
輸出k可能的數值種數,保證至少為1
輸入例子:
9999999999999X
3
輸出例子:
4
題目解析:
這一道題剛拿到的時候第一個反應是使用回溯法來做,恩,然后發現思路不對。而且數據太龐大了,接近18位的數據位,產生的解空間一定大得驚人,然后真的沒想到可以用動態規划來做。
忍不住感嘆動態規划真的很神奇,而且最最關鍵的一步就是找到狀態轉移方程,將問題抽象出來,真的是很奇妙啊。也因此感嘆自己的動態規划學習得有些浮於表面,也缺少練習,在動態規划的優化上也有所欠缺。
這道題目里含有求模的知識點在里面,所以解題的基本思路是基於兩點:
a. (a + b) mod n = (a mod n+ b mod n) mod n; 因此舉個簡單的例子,47%3 =(( 4+4+4……+4)+7)%3 = ((4%3)*10 + 7%3)%3 =((4%3)*10 + 7)%3
b.動規數組dp[i][j]:i 是指第i個數,j是指余數,dp[i][j]表示第i個數產生余數j的可能值的個數,聽起來有點繞,但實際上很巧妙。
注意這里的數組可以簡化到二維數組甚至是一維數組。
[#include<iostream> #include<string> #include<cstring>
using namespace std; //[1] long long 類型 輸出的結果可能會超過 int型 考慮到數位超過18 如果分的人數少,而且X的個數較多 // 如:9XXXXXXXXXXXXXXXXX 1 其結果很容易就超過int的容納量 //[2] 此處為基礎動態規划 數組第二維度 的大小實際上應該根據人數n來決定,但是題目沒有給出來,所以設定較大一個數
long long dp[20][10000]; //18會出錯 19/20
int main() { string str; int n; cin >> str >> n; memset(dp, 0, sizeof(dp)); dp[0][0] = 1;//?
for (int i = 1; i <= str.length(); i++) //從前向后遍歷 (此處應為1 -length();否則i-1報錯,數組溢出)
{ for (int j = 0; j < n; j++) //余數從0 - n;
{ if (str[i-1] == 'X') for (int k = 0; k <= 9; k++) dp[i][(j * 10 + k) % n] += dp[i - 1][j]; else dp[i][(j * 10 + str[i-1] - '0') % n] += dp[i - 1][j]; } } cout << dp[str.length()][0]; return 0; }