假期感觉自己基础还不太扎实就顺便做了下计院实训USACO题组~ 所有Section 1里的题目会在本篇里陆续更新。题目源为洛谷 ,不过和实训内容应该是基本一致的。
P1201 [USACO1.1]你的飞碟在这儿Your Ride Is Here 没啥好说的,入门难度题。注意到’A’的ASCII码 为65,故对字符串里的每个字符减去64就是其对应的数字,乘起来完事。每一次乘都取47的模,防止爆int(貌似不取也不会爆?)。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <cstdio> #include <cstring> #include <iostream> using namespace std ;int main () { char h[7 ],u[7 ]; int hm=1 ,um=1 ,i; cin >>h; cin >>u; int lenh=strlen (h); int lenu=strlen (u); for (i=0 ;i<lenh;i++){ hm*=h[i]-64 ; } hm%=47 ; for (i=0 ;i<lenu;i++){ um*=u[i]-64 ; } um%=47 ; if (um==hm){cout <<"GO" ;} else {cout <<"STAY" ;} return 0 ; }
P1201 [USACO1.1]贪婪的送礼者Greedy Gift Givers 这道题建议使用STL里的map ,程序简洁度和速度都会提高一些。不会的可以取百度,反正之后数据结构里也要学。先读入名字存入map,钱数默认为0。然后再读入送礼人和礼金,将礼金平均加到收礼人账上,最后再在送礼人账上减去实际送出的礼金(25/6=4,但4*6=24,实际要退回一块钱的这种情况)。最后输出每个人账上的钱数即可。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <cstdio> #include <iostream> #include <map> #include <string> using namespace std ;int main () { map <string ,int > giver; int n; string k[10 ]; cin >> n; for (int i = 0 ; i < n;i++){ string z; cin >> z; k[i]=z; giver.insert(make_pair(z,0 )); } for (int i = 0 ; i < n;i++){ int rec,money; string z; cin >> z; cin >> money >> rec; for (int j = 0 ; j < rec;j++){ string t; cin >> t; giver[t] += money / rec; } if (rec){ giver[z] -= (money/rec)*rec; } else { giver[z] += money; } } for (int i=0 ;i<n;i++){ cout << k[i]<<" " <<giver[k[i]]<< "\n" ; } return 0 ; }
P1202 [USACO1.1]黑色星期五Friday the Thirteenth 还在打又长又容易逻辑错误的模拟?这边建议了解下基姆拉尔森计算公式 或蔡勒公式 。利用它们可以很快的算出每月的十三号是星期几。不过我想这种超长公式也不会有人记的,平常可以用用。常规思路是通过这月十三号是星期几推导出下个月十三号是星期几,而非一天天遍历过去。下面给出公式写法。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <cstdio> #include <iostream> using namespace std ;int main () { int n; int day[8 ] = {0 }; cin >> n; for (int i = 1900 ; i < 1900 + n;i++){ for (int j = 1 ; j <= 12 ;j++){ if (j==1 ||j==2 ){ j += 12 ; i--; day[(13 + 2 * j + 3 * (j + 1 ) / 5 + i + i / 4 - i / 100 + i / 400 + 1 ) % 7 ]++; i++; j -= 12 ; } else { day[(13 + 2 * j + 3 * (j + 1 ) / 5 + i + i / 4 - i / 100 + i / 400 + 1 ) % 7 ]++; } } } cout << day[6 ] << " " << day[0 ] << " " ; for (int i = 1 ; i < 6 ;i++){ cout << day[i] << " " ; } }
嗯,贴公式写法是因为懒得打模拟了(逃
P1203 [USACO1.1]坏掉的项链Broken Necklace 正解应该是动态规划。不过350的数据范围?上暴力吧。把项链复制成两倍长就可以当成环遍历了。差不多就是对每个字符左边扫一遍右边扫一遍算出连续相同的珠子加起来,只要存下可收集珠子的最大值就行了。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <cstdio> #include <iostream> using namespace std ;int main () { char lace[800 ]; int n, ans=0 ; cin >> n; cin >> lace; for (int i = 0 ; i < n - 1 ; i++){ lace[n + i] = lace[i]; } lace[n * 2 - 1 ] = '\0' ; for (int i = 0 ; i < n;i++){ int l = 1 , r = 1 ; char judge = lace[i]; while (judge == 'w' && l < n){ judge = lace[i + l]; l++; } while ((lace[i+l]==judge||lace[i+l]=='w' )&&l<n){ l++; } judge = lace[n + i - 1 ]; if (l<n){ r++; while (judge == 'w' && r - 1 < n - l) { judge = lace[n + i - r]; r++; } while ((lace[n+i-r]==judge||lace[n+i-r]=='w' )&&r-1 <n-l) { r++; } } ans = max (ans, r + l - 1 ); } cout << ans; return 0 ; }
我之前写的时候思路也不是很清晰。。所以注释尽量写详细了。。可能还是有点绕吧,大概就是这么个思路就是了(暗示烂代码看不懂很正常)
P3864 [USACO1.2]命名那个数字 Name That Number 肯定不能通过数字枚举可能的字母组合再去查就是了,因为这样最多可能有3的12次方种可能… 不过字典里只有4671行,用字典里的匹配数字的时间是可以接受的!建立一个字符到数字的映射,检索每一个有效名字的映射是否符合数字就OK。下面是洛谷数据的写法(洛谷的字典是标准输入的,PTA上就不清楚啦)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <cstdio> #include <iostream> #include <cstring> using namespace std ;char key[26 ] = {'2' , '2' , '2' , '3' , '3' , '3' , '4' , '4' , '4' , '5' , '5' , '5' , '6' , '6' , '6' , '7' , '0' , '7' , '7' , '8' , '8' , '8' , '9' , '9' , '9' , '0' }; int main () { char c[15 ], z[15 ]; int ans = 0 ; cin >> c; while (cin >> z) { if (strlen (z)!=strlen (c)){ continue ; } else { int f = 1 ; for (int i = 0 ; i < strlen (c);i++){ if (key[z[i]-'A' ]!=c[i]){ f = 0 ; break ; } } if (f){ cout << z << "\n" ; ans++; } } } if (!ans){ cout << "NONE" ; } return 0 ; }
1.2.3 删除排序数组中的重复项 补一道,发现和洛谷上USACO的题不一样。用数组或者vector都行…反正检测上一个存的是否和本次一样就行,一样就不存了,最后输出下标计数就行。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <vector> #include <iostream> using namespace std ;int main () { vector <int > a; int z, i = 1 ; cin >> z; a.push_back(z); while (cin >>z){ if (a[i-1 ]!=z){ a.push_back(z); i++; } } cout << a.size ()-1 ; }
P1204 [USACO1.2]挤牛奶Milking Cows 这道题我用的差分 。可以了解一下。100w的数组是开的下的,所以用一个数组来记录当前时段有多少人在挤奶然后统计下最长无人时间和有人时间就行。注意卡住开始和结束时间,超出该范围的时间不算。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <cstdio> #include <iostream> using namespace std ;int t[1000010 ];int main () { int n, ma = 0 , ze = 0 , on = 0 , mi = 1000010 ; cin >>n; for (int i=0 ;i<n;i++){ int z1,z2; cin >>z1>>z2; t[z1]++; t[z2]--; ma=max (ma,z2); mi = min (z1, mi); } int ze1=0 ,on1=0 ; for (int i=mi;i<=ma;i++){ t[i]=t[i]+t[i-1 ]; if (!t[i]){ze1++;} else {ze=max (ze,ze1); ze1=0 ;} if (t[i]){on1++;} else {on=max (on,on1); on1=0 ;} } cout <<on<<" " <<ze; return 0 ; }
大大大模拟! 做得心态有点爆炸,蛮长一模拟。主要解决的问题是旋转90度和翻转的操作,完成了这两个操作其他就没问题了,180和270可以通过多次旋转90度得到。可以直接遍历矩阵完成变换,也可以从矩阵最外层一层一层向里变换。第一种可能会简单些,画出两个矩阵来推关系吧。后面有心情会整理下推导。 以下给出第二种的做法。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include <cstdio> #include <iostream> using namespace std ;char o[10 ][10 ], t[10 ][10 ], c[10 ][10 ];int n;void rot90 (char b[][10 ]) { char a[10 ][10 ]; for (int i = 0 ; i <= n - 2 ;i++){ for (int j = i; j < n - i;j++){ a[j][n-i-1 ] = b[i][j]; a[n - i - 1 ][n - 1 - j] = b[j][n - i - 1 ]; a[j][i] = b[n - i - 1 ][j]; a[i][n - 1 - j] = b[j][i]; } } for (int i = 0 ; i < n;i++){ for (int j = 0 ; j < n;j++){ b[i][j] = a[i][j]; } } } void reserve () { for (int i = 0 ; i < n/2 ;i++){ for (int j = 0 ; j < n; j++){ swap(t[j][i], t[j][n - i - 1 ]); } } } bool same (char a[][10 ]) { for (int i = 0 ; i < n;i++){ for (int j = 0 ; j < n;j++){ if (a[i][j]!=c[i][j]){ return false ; } } } return true ; } int main () { int j = 1 ; cin >> n; for (int i = 0 ; i < n;i++){ for (int j = 0 ; j < n;j++){ cin >> o[i][j]; t[i][j] = o[i][j]; } } for (int i = 0 ; i < n;i++){ for (int j = 0 ; j < n;j++){ cin >> c[i][j]; } } while (j) { int f = 0 ; for (; j < 4 ;j++){ rot90(t); if (same(t)){ cout << j; f = 1 ; break ; } } if (f){break ;} rot90(t); reserve(); if (same(t)){ cout << j; break ; } for (j = 0 ; j < 4 ; j++) { rot90(t); if (same(t)){ cout << 5 ; f = 1 ; break ; } } if (f){break ;} else if (same(o)){ cout << 6 ; break ; } else { cout << 7 ; break ; } } return 0 ; }
代码还有很多可以优化的地方,比如边旋转边判断,不符合直接跳出,可以节省很多时间。不过这个也能过就是了…
P1206 [USACO1.2]回文平方数 Palindromic Squares 主要搞定俩部分,进制转换 和回文判断 。进制转换 原理是这样的:以十进制为例,如258。 258%10=8,得到个位数。然后258/10=25,你会发现个位被吞掉了,原来的百位变成了个位。以原数大于0为边界,原数没变成0时反复取模和除法,就能从低位到高位得到这个数每一位的数字。你可以发现,其实除数和模数改变成9时,逐位操作得到的就是9进制下该数字的每一位。所以只要把除数和模数改为基数,就能得到该基数下的数字。不过注意,这样的数字是从低位存到高位的(这边建议字符串储存,方便回文判断),也就是直接输出的数字是反转的。回文判断 就很简单了,从数字左端和右端一起向中间扫描,对比是否一致就行。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <cstdio> #include <iostream> using namespace std ;char buf[20 ];char t[10 ] = {'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' };int transfer (int n,int b,char buf[]) { int w = 0 , z; while (n!=0 ) { z = n % b; if (z-9 >0 ){buf[w] = t[z-10 ];} else {buf[w] = z + 48 ;} n /= b; w++; } buf[w] = '\0' ; return w; } bool Palindrome (int n) { for (int i = 0 ; i <= n / 2 ;i++){ if (buf[i]!=buf[n-i-1 ]){ return false ; } } return true ; } int main () { char k[20 ]; int b; cin >> b; for (int i = 1 ; i <= 300 ;i++){ if (Palindrome(transfer (i * i, b, buf))){ int o=transfer (i, b, k); for (int j = o - 1 ; j >= 0 ;j--){cout << k[j];} cout << " " << buf << "\n" ; } } return 0 ; }
P1207 [USACO1.2]双重回文数 Dual Palindromes 上一道题的换皮,没啥好说的了。直接改下上一道题的主程序,函数复制粘贴完事。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <cstdio> #include <iostream> using namespace std ;char buf[32 ];int transfer (int n,int b) { int w = 0 ; while (n!=0 ) { buf[w] = n % b + 48 ; n /= b; w++; } buf[w] = '\0' ; return w; } bool Palindrome (int n) { for (int i = 0 ; i <= n / 2 ;i++){ if (buf[i]!=buf[n-i-1 ]){ return false ; } } return true ; } int main () { int n, s, ans = 0 ; cin >> n >> s; for (int i = s + 1 ; ans < n;i++){ int c = 0 ; for (int j = 2 ; j <= 10 ;j++){ if (Palindrome(transfer (i,j))){ c++; } } if (c>=2 ){ cout << i << "\n" ; ans++; } } }
P1208 [USACO1.3]混合牛奶 Mixing Milk 贪心+排序 ,把所有奶农按价钱从低到高排序,遍历直到满足牛奶需求即可。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <cstdio> #include <iostream> #include <algorithm> using namespace std ;struct farmer //结构体存奶农数据{ int price, num; }; bool cmp (farmer a,farmer b) { return a.price < b.price; } farmer a[5010 ]; int main () { int n, m, ans = 0 , milk = 0 , c = 0 ; cin >> n >> m; for (int i = 0 ; i < m;i++){ cin >> a[i].price >> a[i].num; } sort(a, a + m, cmp); while (milk<n) { ans += a[c].num * a[c].price; milk += a[c].num; c++; } c--; if (milk>n){ ans -= (milk - n) * a[c].price; } cout << ans; }
P1209 [USACO1.3]修理牛棚 Barn Repair 贪心+排序 ,比上一题要绕个弯。将连在一起的牛棚看作一个整体,显然,这个整体数比间隔数多1。在不考虑木板限制的情况下,毫无疑问,每个整体用一块木板时,长度最短。然后我们发现,每连接一个间隔,需要的木板数就会减少1——所以这道题其实是对间隔贪心排序,优先填补最小的间隔,直到木板数小于等于限制木板数。最后的答案就是填补的间隔总长+有牛的牛棚数量。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <cstdio> #include <iostream> #include <algorithm> using namespace std ;int main () { int m, s, c, gap[200 ], g = 0 , ans = 0 , cow[201 ]; cin >> m >> s >> c; for (int i = 0 ; i < c;i++){ cin >> cow[i]; } sort(cow, cow + c); for (int i = 1 ; i < c;i++){ if (cow[i] - cow[i-1 ] > 1 ) { gap[g] = cow[i] - cow[i-1 ] - 1 ; g++; } } sort(gap, gap + g); int w = g + 1 ; for (int i = 0 ; w > m;i++){ ans += gap[i]; w--; } cout << ans + c; return 0 ; }
P1210 [USACO1.3]最长的回文 Calf Flac 不同于前面两道回文,这道题需要在给定文本中找出回文。最优解法是Manacher算法 ,O(n)的时间内求解。常规算法就是遍历整个字符串,向每个字符两边扩展回文,需要分奇扩展和偶扩展(奇回文对称中心为回文中心的字符,而偶回文对称中心在中间两个字母之间)。另外这道题的输入也需要用特殊方法处理,详见代码。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <cstdio> #include <iostream> #include <cstring> using namespace std ;char origin[200010 ], z[88 ], tr[200010 ];int zpos[2 ], o[200010 ], length;int Palindromej (int c) { int len = 0 , i = 1 ; while (c-i>=0 && c+i<length && len<2002 ) { if (tr[c-i]==tr[c+i]){len+=2 ; i++;} else {break ;} } i--; zpos[0 ] = o[c - i]; zpos[1 ] = o[c + i]; return len + 1 ; } int Palindromeo (int c) { int len = 0 , i = 1 ; while (c-i>=0 && c+i-1 <length && len<2002 ) { if (tr[c-i]==tr[c+i-1 ]){len+=2 ; i++;} else {break ;} } i--; zpos[0 ] = o[c - i]; zpos[1 ] = o[c + i - 1 ]; return len; } int main () { int ma = 0 , pos[2 ], t = 0 ; while (cin .getline(z,88 )) { strcat (origin, z); strcat (origin, "\n" ); } for (int i = 0 ; i < strlen (origin);i++){ if ((origin[i]>='A' && origin[i]<='Z' )||(origin[i]>='a' && origin[i]<='z' )){ tr[t] = tolower (origin[i]); o[t] = i; t++; } } length = strlen (tr); for (int i = 1 ; i < length;i++){ int m = Palindromej(i); if (m>ma){ ma = m; pos[0 ] = zpos[0 ]; pos[1 ] = zpos[1 ]; } m = Palindromeo(i); if (m>ma){ ma = m; pos[0 ] = zpos[0 ]; pos[1 ] = zpos[1 ]; } } cout << ma << endl ; for (int i = pos[0 ]; i <= pos[1 ];i++){ cout << origin[i]; } return 0 ; }