单调数据结构的其他应用

单调栈一般用来求解 NGE (Next Greater Element) 和类似问题,单调队列一般用来求解区间 \(\min/\max\),但事实上,这种思想还有更灵活的运用方式。

P3503 [POI2010]Blocks

给定一个长为 \(n\) 的整数序列 \(a\),有 \(m\) 次询问,每次给出一个正整数 \(k\),可以进行如下操作:

  • 每次选择一个大于 \(k\) 的正整数 \(a_i\),将 \(a_i\) 减去 \(1\) ,选择 \(a_{i-1}\)\(a_{i+1}\) 中的一个加上 \(1\)

经过若干次操作后,问最长能够选出多长的一个区间,使得这个区间的每个数都不小于 \(k\)

\(n\le 10^6,m\le 50\)

\(m\) 很小,考虑单次询问 \(O(n)\) 求解。

首先注意到,一个区间合法(可以通过操作使得每个数都 \(\ge k\))等效于这个区间平均值 \(\ge k\)

感性理解,这个操作相当于一个元素把自己的值送给了与它相邻的元素,总和不变。如果平均值 \(\ge k\) 且存在一个 \(<k\) 的元素,根据抽屉原理必然存在一个 \(>k\) 的元素,这个大数一定可以把自己多余的部分转移到那个小数上,所以合法。

问题转化为找到最大的 \(r-l+1\) 使得 \(\sum_{i=l}^ra_i\ge (r-l+1)k\),变形得到 \(\sum_{i=l}^r(a_i-k)\ge 0\)

\(a_i\leftarrow a_i-k,l\leftarrow l-1\),构造前缀和序列 \(s\),式子化为 \(s_r-s_l\ge 0\)

平衡树能做,但是多一个大常数 \(\log\),数据范围这么大显然过不去。

考虑什么样的 \(\boldsymbol l\)\(\boldsymbol r\) 是没用的

显然如果 \(i<j\land s_i\le s_j\),则 \(j\) 不可能作为 \(l\),同理 \(i\) 也不能作为 \(r\)

于是我们发现合法的 \(l\)\(r\) 都满足对应的 \(s\) 值递减。求合法的 \(l\) 要从左到右枚举,求合法的 \(r\) 要从右到左枚举。

在枚举 \(r\) 的过程中,\(s\) 是增大的,因此之前合法的 \(l\) 现在依旧合法,所以考虑先处理出合法的 \(l\) 即可做到线性。

#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
#define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 1e6 + 5;
int n, m, a[N], s[N];
signed main() {
#ifdef ONLINE_JUDGE
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
#endif
    cin >> n >> m;
    rep(i, 1, n) cin >> a[i];
    while(m--) {
        int k; cin >> k;
        rep(i, 1, n) s[i] = s[i - 1] + a[i] - k;
        stack<int, vector<int>> stk;
        stk.push(0);
        rep(i, 1, n) if(s[i] < s[stk.top()]) stk.push(i);
        int ans = 0, las = -1e18;
        per(i, n, 1) {
            if(s[i] <= las) continue;
            while(!stk.empty() && s[i] >= s[stk.top()]) {
                gmax(ans, i - stk.top());
                stk.pop();
            }
            las = s[i];
        }
        cout << ans << ' ';
    }
    return 0;
}

P4954 [USACO09OPEN] Tower of Hay G

给定一个长为 \(n\) 的正整数序列 \(a\),你需要把它划分成 \(k\) 个子区间 \([l_1,r_1],[l_2,r_2],\dots,[l_k,r_k]\),使得 \(l_1=1,r_k=n,\forall i\in[1,k-1],r_i+1=l_{i+1},\sum_{j=l_i}^{r_i}a_i\le\sum_{j=l_{i+1}}^{r_{i+1}}a_i\),问最大的 \(k\)

有一个从后往前的贪心,即最后一个单独分一组,前面的如果能分就分最短的,可以拿到 36 分。

虽然贪心是错的,但这给了我们一些启发,考虑从后往前 DP。设 \(f_i\) 表示 \(i\) 所在的组以 \(i\) 结尾时最多能分多少组,\(g_i\) 表示此时 \(i\) 所在的组是多少。设 \(s\) 为后缀和,得到转移 \(f_i=f_j+1,g_i=s_i-s_j\),其中 \(j\) 为最小的满足 \(s_i-s_j\ge g_j\) 的数,正确性在于 \(f\) 是不降的。

于是我们得到了一个 \(O(n^2)\) 的做法,由于数据水可以过,平衡树可以优化到 \(O(n\log n)\),但是还有一个 \(O(n)\) 做法。

依然是考虑什么样的 \(\boldsymbol j\) 是没用的

移项得到 \(s_j+g_j\le s_i\),左边变成了一个只和 \(j\) 有关的式子,设 \(v_j=s_j+g_j\)。如果 \(j>k\land v_j\ge v_k\),那么 \(j\) 就没用了。

考虑维护合法的 \(j\),注意到 \(val_j\) 是递增的,\(f\) 也是递增的,所以之前合法的 \(j\) 现在依然合法,指针的移动是单向的,单调队列维护即可。

#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
#define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 1e5 + 5;
int n, a[N], s[N], f[N], g[N];
inline int val(int i) {
    return s[i] + g[i];
}
signed main() {
#ifdef ONLINE_JUDGE
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
#endif
    cin >> n;
    rep(i, 1, n) cin >> a[i];
    per(i, n, 1) s[i] = s[i + 1] + a[i];
    deque<int> q;
    q.push_back(n + 1);
    per(i, n, 1) {
        int t = q.back();
        while(!q.empty() && val(q.back()) <= s[i]) 
            t = q.back(), q.pop_back();
        f[i] = f[t] + 1;
        g[i] = s[i] - s[t];
        q.push_back(t);
        while(!q.empty() && val(q.front()) >= val(i))
            q.pop_front();
        q.push_front(i);
    }
    cout << f[1] << endl;
    return 0;
}
posted @ 2023-07-04 08:20  untitled0  阅读(23)  评论(0)    收藏  举报