1. 551. Student Attendance Record I
2. 552. Student Attendance Record II
hihocode原题,https://hihocoder.com/contest/offers10/problems
直接粘贴代码。
其实这个第二题的问题挺多的,这里还是写一下分析过程。
我刚开始做的时候,考虑也是计数dp,一维记录a的个数,一维记录末尾l的个数,然后考虑转移,后来好像是因为转移情况复杂,简化为不考虑a的个数,因为a的个数只有1个,所以可以单独考虑,然后考虑只有p和l的情况,这样的问题应该是很好考虑的,我写的有点
复杂,我是记录末尾是什么,其实只用考虑末尾l的个数就行,只用一维就可以解决,而我使用了2维,枚举最后的2个字符是什么,还是比较繁琐的。计算过程,就是状态的转移过程,很好理解。
接下来考虑a的情况,最后的结果肯定先要加上没有a的情况,然后枚举a的位置,然后左右2边就又变成上面没a的情况,把左和右乘起来,就是最后的结果。
我的做法不具有通用性,而且绕的弯子有点大,没有抓住问题的本质。
接下来考虑正常的做法,计数dp,一维记录a的个数,一维记录末尾l的个数,然后考虑转移的情况。
转移的详细分析:http://bookshadow.com/weblog/2017/04/16/leetcode-student-attendance-record-ii/。
其实自己手动分析,其实也是很简单的。
注意:上面的考虑的是,谁可以转移到我,这是一个收集的过程,分析可能的情况,而且情况数目很多,代码量大,很容易出错。
解法二:
1 #include<bits/stdc++.h> 2 #define pb push_back 3 typedef long long ll; 4 using namespace std; 5 typedef pair<int, int> pii; 6 const int maxn = 1e5 + 10; 7 const int mod = 1e9 + 7; 8 ll dp[maxn][2][3]; 9 int n; 10 void add(ll& x, ll y) { 11 x = (x + y) % mod; 12 } 13 void solve() { 14 cin >> n; 15 dp[0][0][0] = 1; 16 for (int i = 1; i <= n; i++) { 17 add(dp[i][0][0], dp[i - 1][0][0]); 18 add(dp[i][0][0], dp[i - 1][0][1]); 19 add(dp[i][0][0], dp[i - 1][0][2]); 20 21 22 add(dp[i][0][1], dp[i - 1][0][0]); 23 add(dp[i][1][1], dp[i - 1][1][0]); 24 25 add(dp[i][0][2], dp[i - 1][0][1]); 26 add(dp[i][1][2], dp[i - 1][1][1]); 27 28 add(dp[i][1][0], dp[i - 1][1][0]); 29 add(dp[i][1][0], dp[i - 1][1][1]); 30 add(dp[i][1][0], dp[i - 1][1][2]); 31 add(dp[i][1][0], dp[i - 1][0][0]); 32 add(dp[i][1][0], dp[i - 1][0][1]); 33 add(dp[i][1][0], dp[i - 1][0][2]); 34 35 } 36 ll res = 0; 37 for (int i = 0; i < 2; i++) 38 for (int j = 0; j < 3; j++) 39 add(res, dp[n][i][j]); 40 cout << res << endl; 41 } 42 43 int main() { 44 freopen("test.in", "r", stdin); 45 //freopen("test.out", "w", stdout); 46 ios::sync_with_stdio(0); 47 cin.tie(0); cout.tie(0); 48 solve(); 49 return 0; 50 }
转移复杂,需要考虑的情况多。
然后我参考排行榜第一名的代码,就有下面的分析:
所以就有下面的分析:其实应该考虑的是:我可以转移到谁,这是一个分发的过程,因为当前字符可能为a,p, l,所以是很好考虑的,代码也相对好写些。
解法三:
1 #include<bits/stdc++.h> 2 #define pb push_back 3 typedef long long ll; 4 using namespace std; 5 typedef pair<int, int> pii; 6 const int maxn = 1e5 + 10; 7 const int mod = 1e9 + 7; 8 ll dp[maxn][2][3]; 9 int n; 10 void add(ll& x, ll y) { 11 x = (x + y) % mod; 12 } 13 void solve() { 14 cin >> n; 15 dp[0][0][0] = 1; 16 for (int i = 0; i < n; i++) { 17 for (int x = 0; x < 2; x++) { 18 for (int y = 0; y < 3; y++) { 19 ll cur = dp[i][x][y]; 20 if(cur == 0) continue; 21 add(dp[i + 1][x][0], cur); 22 if(x + 1 < 2) { 23 add(dp[i + 1][x + 1][0], cur); 24 } 25 if(y + 1 < 3) 26 add(dp[i + 1][x][y + 1], cur); 27 } 28 } 29 } 30 ll res = 0; 31 for (int i = 0; i < 2; i++) 32 for (int j = 0; j < 3; j++) 33 add(res, dp[n][i][j]); 34 cout << res << endl; 35 } 36 37 int main() { 38 freopen("test.in", "r", stdin); 39 //freopen("test.out", "w", stdout); 40 ios::sync_with_stdio(0); 41 cin.tie(0); cout.tie(0); 42 solve(); 43 return 0; 44 }
上面的代码都可以采用滚动数组优化。
其实分析到这里就差不多了,更详细的做法是贴出代码,一步一步解释,但是那样实在是太麻烦了,许多事情还是需要自己去走一遍的。
上面算是3种方法,第三种是最好的。
这个题目的n的限制为1e5,其实如果为1e8, 可以做么,其实是可以的,搞出来一个转移矩阵,然后快速幂来解决的,这也算是通用的套路吧。
转移矩阵的写法:
1 #include<bits/stdc++.h> 2 #define pb push_back 3 typedef long long ll; 4 using namespace std; 5 typedef pair<int, int> pii; 6 const int maxn = 1e3 + 10; 7 const int mod = 1e9 + 7; 8 struct mat { 9 ll a[6][6]; 10 mat() { 11 memset(a, 0, sizeof a); 12 } 13 void pr() { 14 for (int i = 0; i < 6; i++) { 15 for (int j = 0; j < 6; j++) 16 cout << a[i][j] << " "; 17 cout << endl; 18 } 19 } 20 }; 21 mat mul(mat& a, mat& b) { 22 mat c; 23 for (int i = 0; i < 6; i++) { 24 for (int j = 0; j < 6; j++) { 25 for (int k = 0; k < 6; k++) { 26 c.a[i][j] = (c.a[i][j] + a.a[i][k] * b.a[k][j] % mod) % mod; 27 } 28 } 29 } 30 return c; 31 } 32 mat tr, one; 33 int f(int x, int y) { 34 return x * 3 + y; 35 } 36 void init() { 37 for (int i = 0; i < 2; i++) { 38 for (int j = 0; j < 3; j++) { 39 int cur = f(i, j); 40 one.a[cur][cur] = 1; 41 tr.a[cur][f(i, 0)] = 1; 42 if(i + 1 < 2) 43 tr.a[cur][f(i + 1, 0)] = 1; 44 if(j + 1 < 3) 45 tr.a[cur][f(i, j + 1)] = 1; 46 } 47 } 48 49 } 50 int n; 51 void solve() { 52 cin >> n; 53 init(); 54 //tr.pr(); 55 //one.pr(); 56 mat res; 57 res.a[0][0] = 1; 58 while(n) { 59 if(n & 1) one = mul(one, tr); 60 n >>= 1; 61 tr = mul(tr, tr); 62 } 63 res = mul(res, one); 64 ll r = 0; 65 for (int i = 0; i < 6; i++) 66 r = (r + res.a[0][i]) % mod; 67 cout << r << endl; 68 } 69 70 int main() { 71 freopen("test.in", "r", stdin); 72 //freopen("test.out", "w", stdout); 73 ios::sync_with_stdio(0); 74 cin.tie(0); cout.tie(0); 75 solve(); 76 return 0; 77 }
相关的题目:
https://hihocoder.com/contest/offers13/problem/4 需要搞出状态转移方程,然后快速幂解决。
https://codejam.withgoogle.com/codejam/contest/10214486/dashboard#s=p3 这个难度比较高,也是转移方程的构造。
就这样吧,写了这么多啰嗦的话,本来只是想贴一下自己写的。
同时也暴露出一个问题,题目不是自己ac就完了,需要看一下别人是怎么做的,怎么想的,有没有什么巧妙的思路,这个过程才是关键的,这样才能提高和进步。
1 class Solution { 2 public: 3 bool checkRecord(string s) { 4 int n = s.size(); 5 int a = 0; 6 for (int i = 0; i < n; i++) { 7 if(s[i] == 'A') { 8 a++; 9 if(a > 1) return 0; 10 } 11 if(s[i] == 'L') { 12 if(i + 2 < n && s[i + 1] == 'L' && s[i + 2] == 'L') 13 return 0; 14 } 15 } 16 return 1; 17 } 18 };
1 typedef long long ll; 2 const int maxn = 1e5 + 10; 3 const int mod = 1e9 + 7; 4 ll dp[maxn][2][2]; 5 ll s[maxn]; 6 class Solution { 7 public: 8 int checkRecord(int n) { 9 if(n == 1) { 10 //cout << 3 << endl; 11 return 3; 12 } 13 if(n == 2) { 14 //cout << 8 << endl; 15 return 8; 16 } 17 // O L A 18 // 0 1 2 19 memset(s, 0, sizeof s); 20 memset(dp, 0, sizeof dp); 21 for (int i = 0; i < 2; i++) 22 for (int j = 0; j < 2; j++) 23 dp[2][i][j] = 1; 24 for (int c = 3; c <= n; c++) { 25 for (int i = 0; i < 2; i++) { 26 for (int j = 0; j < 2; j++) { 27 for (int x = 0; x < 2; x++) { 28 if(i == 1 && j == 1 && x == 1) { 29 continue; 30 } 31 dp[c][j][x] = (dp[c][j][x] + dp[c - 1][i][j]) % mod; 32 } 33 } 34 } 35 for (int i = 0; i < 2; i++) { 36 for (int j = 0; j < 2; j++) { 37 s[c] = (s[c] + dp[c][i][j]) % mod; 38 } 39 } 40 } 41 ll res = 0; 42 res = s[n]; 43 s[0] = 1; 44 s[1] = 2; 45 s[2] = 4; 46 for (int i = 1; i <= n; i++) { 47 int left = i - 1, right = n - i; 48 res = (res + s[left] * s[right] % mod) % mod; 49 } 50 return res; 51 52 } 53 };
3. 553. Optimal Division
刚开始,看到长度为10,以为是暴力枚举,因为枚举每一个位置的优先级,然后计算,但是仔细想想,要是实现起来还是比较麻烦的。考虑到是第二题,不会这么麻烦,考虑
数学的分析方法,结果最大,除数最小,什么时候最小,当然是连除,越除越小,然后答案就很明显了。直接输出结果。
1 class Solution { 2 public: 3 string optimalDivision(vector<int>& nums) { 4 int n = nums.size(); 5 stringstream ss; 6 if(n == 1) { 7 ss << nums[0]; 8 } else if(n == 2) { 9 ss << nums[0] << "/" << nums[1]; 10 } else { 11 ss << nums[0] << "/(" << nums[1]; 12 for (int i = 2; i < n; i++) 13 ss << "/" << nums[i]; 14 ss << ")"; 15 } 16 return ss.str(); 17 } 18 };
4. 555. Split Concatenated Strings
说实话,看完真的一点想法也没有,完全懵逼。
静下心来分析,考虑整个字符串长度为1000,那么可以简单的枚举每一个字符串的开始,每一个字符都有可能作为开头,接下来考虑,固定头部的时候,其他的字符串怎么办,其实是很明显的,因为其他字符串只有顺序和逆序2种情况,而且要使得结果最大,肯定选择2种里面较大的那一种,显然是确定的,所以枚举的每一个起始字符以后,可以在O(n)的复杂度内,计算出这个串,然后不断的更新结果,就可以了。
1 class Solution { 2 public: 3 string splitLoopedString(vector<string>& v) { 4 int n = v.size(); 5 if(n == 0) return ""; 6 string res; 7 for (string&t : v) { 8 string td = t; 9 reverse(td.begin(), td.end()); 10 if(td > t) 11 t = td; 12 res += t; 13 } 14 //cout << res << endl; 15 for (int i = 0; i < n; i++) { 16 int sz = v[i].size(); 17 for (int j = 0; j < sz; j++) { 18 string ml = v[i].substr(j, sz - j), mr = v[i].substr(0, j); 19 string rl = v[i].substr(j + 1, sz - j - 1), rr = v[i].substr(0, j + 1); 20 reverse(rl.begin(), rl.end()); 21 reverse(rr.begin(), rr.end()); 22 // ml mr 23 // rr rl 24 for (int j = (i + 1) % n; j != i; j = (j + 1) % n) { 25 ml += v[j]; 26 rr += v[j]; 27 } 28 29 ml += mr; 30 rr += rl; 31 //cout << ml << endl; 32 //cout << rr << endl; 33 res = max(res, max(ml, rr)); 34 } 35 } 36 return res; 37 } 38 };