题目链接:http://219.244.176.199/JudgeOnline/problem.php?id=1213
这是这次微软实习面试的一道题,当时只相出了一个2n的做法,面试官让我优化成n的做法。不过当时没想出来,最后面试官提示到了最后才恍然大悟。
题目大意就是在一个环上有n个加油站,已知第i个加油站可以提供ai的油量,从第i个加油站到第i+1个加油站需要消耗bi的油量。(当i为n时,则表示n到1的油量)
问:是否能从某个加油站出发,绕环一圈回到这个加油站?(当途中到达某个加油站时的油量小于0则不可)。
首先可以设f(i) = a(i)-b(i),然后就可以转换成,在某个点出发回到这个点的过程中f的sum值恒大于等于0。然后就联系到最大子序列和,如果从最大子序列的起点处出发都不能绕一圈,那么显然任何位置都不行,因为任何位置出发经过最大子序列时,必然sum值小于最大子序列和,这样的话后面必然某处会出现小于0的情况。不然的话,这个位置就能构成更大的子序列,就矛盾了。
于是,我需要计算出环上的最大子序列,我想到的方法便是倍长一下,然后再求,于是就变成了2n的做法。代码中可以稍微优化一下就不需要f数组了。
关于O(n)的做法,其实也挺好想的,不过当时面试的时候,一紧张就思维混乱了。
首先如果从某个i出发,到j的时候sum(i, j)小于0了。那么[i ,j]间所有的点k出发到j处的sum(k, j)都会小于0,因为i能到k,自然sum(i, k)大于等于0,而sum(i, j) = sum(i, k-1)+sum(k, j),必然sum(k, j)要更小一点。那么[i, j]间的点都可以排除了。于是可以直接从0点出发绕一圈,遇到i不行,就继续从i+1出发,直到能跑完或者跑到n的位置处。
代码(n的做法):
#include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <algorithm> #include <set> #include <map> #include <queue> #include <vector> #include <string> #define LL long long using namespace std; const int maxN = 100005; int n, a[maxN], b[maxN]; void input() { for (int i = 0; i < n; ++i) scanf("%d", &a[i]); for (int i = 0; i < n; ++i) scanf("%d", &b[i]); } void work() { int rest; bool flag; for (int i = 0; i < n; ++i) { flag = true; rest = 0; int j; for (j = 0; j < n; ++j) { if (rest < 0) { flag = false; break; } rest += a[(i+j)%n]-b[(i+j)%n]; } if (flag && rest >= 0) { printf("Yes "); return; } else i = min(i+j, n); } printf("No "); } int main() { //freopen("test2.in", "r", stdin); //freopen("test2.out", "w", stdout); while (scanf("%d", &n) != EOF) { input(); work(); } return 0; }
代码(2n的做法):
#include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <cstring> #include <algorithm> #include <set> #include <map> #include <queue> #include <vector> #include <string> #define LL long long using namespace std; const int maxN = 100005; int n, a[maxN], b[maxN]; void input() { for (int i = 0; i < n; ++i) scanf("%d", &a[i]); for (int i = 0; i < n; ++i) scanf("%d", &b[i]); } void work() { int maxFrom, maxSum = 0; int from = 0, sum = 0, len = 2*n-1; for (int i = 0; i < len; ++i) { if (sum >= 0) { sum += a[i%n]-b[i%n]; if (sum > maxSum) { maxSum = sum; maxFrom = from; } } else { sum = a[i%n]-b[i%n]; from = i%n; } } sum = 0; for (int i = 0; i < n; ++i) { if (sum < 0) { printf("No "); return; } sum += a[(maxFrom+i)%n]-b[(maxFrom+i)%n]; } if (sum >= 0) printf("Yes "); else printf("No "); } int main() { //freopen("test.in", "r", stdin); //freopen("test2.out", "w", stdout); while (scanf("%d", &n) != EOF) { input(); work(); } return 0; }