题意:
给出一个01串,选一个长度至少为L的连续子串,使得串中数字的平均值最大。
分析:
能把这道题想到用数形结合,用斜率表示平均值,我觉得这个想法太“天马行空”了
首先预处理子串的前缀和sum,如果在坐标系中描出(i, sum[i])这些点的话。
所求的平均值就是两点间的斜率了,具体来说,在连续子串[a, b]中,有sum[b]-sum[a-1]个1,长度为b-a+1,所以平均值为(sum[b]-sum[a-1])/(b-a+1)
所以就把问题转化为:求两点横坐标之差至少为L-1,能得到的最大斜率。
这道题和HDU 5033很相似,当时第一次见到用单调栈的解法,仅仅是凭着自己的理解写的题解,现在看来写得也十分蛋疼。
还是紫书上讲得清楚透彻。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 5 const int maxn = 100000 + 10; 6 char s[maxn]; 7 int n, L, sum[maxn], p[maxn]; 8 9 int cmp(int a1, int b1, int a2, int b2) 10 { return (sum[b1]-sum[a1-1]) * (b2-a2+1) - (sum[b2]-sum[a2-1]) * (b1-a1+1); } 11 12 int main() 13 { 14 int T; 15 scanf("%d", &T); 16 while(T--) 17 { 18 scanf("%d%d", &n, &L); getchar(); 19 for(int i = 1; i <= n; ++i) s[i] = getchar(); 20 for(int i = 1; i <= n; ++i) sum[i] = sum[i-1] + s[i] - '0'; 21 22 int i = 0, j = 0, ansL = 0, ansR = L; 23 for(int t = L; t <= n; ++t) 24 {//枚举连续子串的右端点 25 while(j - i > 1 && cmp(p[j-2], t-L, p[j-1], t-L) >= 0) j--;//删掉上凸点 26 p[j++] = t-L+1; 27 28 while(j - i > 1 && cmp(p[i], t, p[i+1], t) <= 0) i++;//找到切点使斜率最大 29 int c = cmp(p[i], t, ansL, ansR); 30 if(c > 0 || (c == 0 && t - p[i] < ansR - ansL)) { ansL = p[i]; ansR = t; } 31 } 32 33 printf("%d %d ", ansL, ansR); 34 } 35 36 return 0; 37 }