/* 这题的思路据是二分+贪心,二分枚举所有解,贪心判断能否考完 注意,这题自己写代码时,出了一个很隐蔽的bug,当时本来已经把变量 n、m 定义为全局变量了,但居然在main函数里又定义了一次,这就相当于屏蔽了全局变量,虽然输入是正确的,但是,在judge()函数判断能否考完的过程中,所用的n、m都是错的(因为此时真正的是作为局部变量出现的,但judge()函数种使用的为全局) 以及注意,一般情况下,是不建议定义全局变量的,这题其实不定义也未尝不可,只是judge()函数就要传递3个参数了 BTW,其实入门经典里有提到过,全局变量被屏蔽,可能会导致错误。我以为我还算是仔细,今天看来,其实我也还是很粗心的... 最初写这题总结的时候,我还不会二分法,参考了blog: http://www.cnblogs.com/westwind1005/p/5976882.html */
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; int a[N]; int d[N]; int pass[N]; int n, m; int judge(int x) { memset(pass, 0, sizeof(pass)); int pre = x - 1; for (int i = x; i >= 1; i--) //从后往前遍历天数 { pre = min(pre, i - 1); //不断更新为这门考试的准备时间,最多也只能是i - 1天的复习时间 if (d[i] && !pass[d[i]] && a[d[i]] <= pre) { pass[d[i]] = 1; pre -= a[d[i]] + 1; //+1为下次考试所占的一天,若下次要考试,则下次的准备时间,除了要减去这次考试的准备时间,还要减去下次作为考试的那天 } } for (int i = 1; i <= m; i++) if (!pass[i]) return 0; return 1; } int main() { while (cin >> n >> m) { int ans = -1; for (int i = 1; i <= n; i++) cin >> d[i]; for (int i = 1; i <= m; i++) cin >> a[i]; int l = 1,r = n; while (l <= r) { int mid = (l + r) >> 1; if (judge(mid)) { ans = mid; r = mid - 1; } else { l = mid + 1; } } cout << ans << endl; } return 0; }
/* 法二 后来在另外一个blog上,看到judge函数的另外一种写法,这种写法比较容易理解,所以我也自己写了一次 思路来自: http://blog.csdn.net/qq_34374664/article/details/52853212 BTW,上面那篇博客里,博主对这题的解释十分详细,比我自己的代码的解释详细多了... */
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; int a[N]; int d[N]; int pass[N]; int n, m; int judge(int x) { memset(pass, 0, sizeof(pass)); int sum = 0, cnt = 0; // cnt 用来记录通过的科目数,sum用来目前还需要在前面找多少天复习 for (int i = x; i >= 1; i--) //从后往前遍历天数 { if (d[i] && !pass[d[i]]) // 如果可以考试且这门课没考,则考 { pass[d[i]] = 1; sum += a[d[i]]; cnt++; } else if (sum > 0) //如果还需要从前面抽出时间复习,那么就用这天复习,否则就当作休息;注意一定要当前需要前面sum天复习,sum才能自减,如果sum本来就为0,就不可减了,否则WA { sum--; } } if (sum > 0|| cnt < m) return 0; return 1; } int main() { while (cin >> n >> m) { int ans = -1; for (int i = 1; i <= n; i++) cin >> d[i]; for (int i = 1; i <= m; i++) cin >> a[i]; int l = 1,r = n; while (l <= r) { int mid = (l + r) >> 1; if (judge(mid)) { ans = mid; r = mid - 1; } else { l = mid + 1; } } cout << ans << endl; } return 0; }
/* 法三: 法三利用了栈,我觉得是三种写法总最容易懂的,但似乎也就没有前两种简洁了。 这三种方法,本质上是一样的,只是写法的区别,思路上差异不大 */
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; int a[N]; int pass[N]; int n, m; struct day { int test, have; //变量分别表示:当天能考的科目,已为该科目准备了多久 day() { have = 0; } }d[N]; int judge(int x) { int ans = 0; // 记录有几门科目考完 memset(pass, 0, sizeof(pass)); stack<day> s; day now; // 当前天数 for (int i = x; i >= 1; i--) { if (d[i].test && !pass[d[i].test]) // 从后往前遍历,如果某天有考试,且还没考,那就考这科 { pass[d[i].test] = 1; s.push(d[i]); } else if ( !s.empty() ) // 否则,取出最近的一次(没准备好)的考试,这天用来准备 { now = s.top(); s.pop(); now.have++; if ( now.have == a[now.test] ) //如果需要的准备时间,全都准备好了,则真正出栈。否则,准备时间子增后,再次入栈 ans++; else s.push(now); } } if (ans == m) return 1; return 0; } int main() { while (cin >> n >> m) { int ans = -1; for (int i = 1; i <= n; i++) cin >> d[i].test; for (int i = 1; i <= m; i++) cin >> a[i]; int l = 1,r = n; while (l <= r) { int mid = (l + r) >> 1; if (judge(mid)) { ans = mid; r = mid - 1; } else { l = mid + 1; } } cout << ans << endl; } return 0; }