
给定一串01序列,寻找某一段区间中1的个数与区间长度的比值最大,且区间长度大于等于L。
首先,求区间内1的个数肯定用前缀和处理。
问题转换为有一个离散的函数f(x) ,求函数中间隔大于等于L的任意两点的最大斜率。
画出图来观察,发现当有三个点组成了上凸时,凸点与这三点右边的任意点的斜率一定小于等于那两个非凸点。所以每次循环中在求斜率之前,先把集合中整理一遍,把所有凸点都删除。
用数组模拟双端队列,每次新加入一个点之前,先把队列中的凸点都去掉,使得队列中的相邻两点斜率是递增的,形成单调队列。每次都会检查上一次新加入的点,若上一次加入的点与它左端的点斜率小于等于1,则把它删掉,否则这一轮再加入点后,上一次加入的点就会成为凸点。
每次都确保在非凸点中寻找答案。由于从左到右斜率会先变大后变小,所以当斜率停止变小,即当当前点右边的点与目标点的斜率大于等于当前点与目标点的斜率时,停止搜索。
因为只有当总的点数大于等于3时,才会有凸点,所以在新加点之前应确保已有点数大于等于2。
由于每次要求答案斜率更大,所以每次搜索的左端点一定是非递减的,否则斜率一定会小于等于当前答案。这就限制了搜索范围,设置双端队列的开头为搜索起点,每一轮搜索完后都更新为这一次的斜率最大点,即切点。
由于每个点都只会被搜索或删除一次,所以复杂度为O(n)。
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
| #include<bits/stdc++.h> using namespace std; #define ll long long typedef pair<int, int> pii; const int maxn = 1e5 + 5; int T, n, L; int a[maxn], p[maxn]; int cmp_k(int x1, int x2, int x3, int x4) { return (a[x2] - a[x1 - 1])*(x4 - x3 + 1) - (a[x4] - a[x3 - 1])*(x2 - x1 + 1); } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> T; while (T--) { cin >> n >> L; for (int i = 1; i <= n; i++) { char c; cin >> c; a[i] = a[i - 1] + c - '0'; } int ansl = 1, ansr = L; int l = 0, r = 0; for (int i = L; i <= n; i++) { while (r - l > 1) { if (cmp_k(p[r - 2], i - L, p[r - 1], i - L) >= 0)r--; else break; } p[r++] = i - L + 1; while (r - l > 1) { if (cmp_k(p[l], i, p[l + 1], i) <= 0)l++; else break; } int k = cmp_k(ansl, ansr, p[l], i); if (k < 0 || k == 0 && i - p[l] < ansr - ansl) { ansr = i; ansl = p[l]; } } cout << ansl << ' ' << ansr << endl; } return 0; }
|