2022河南萌新联赛第(一)场 :河南工业大学 H(二分st表,离线线段树)
2022河南萌新联赛第(一)场 :河南工业大学 H(二分st表,离线线段树)
题意
同学们在实验室玩游戏,每个人有一个兴奋值 \(a_i\) ,但是这时候教练走过来了。教练有多次询问,每次会询问在一个区间的最大兴奋值。每个询问会影响在当前询问区间 [l,r][l,r][l,r] 内的人的兴奋值,只影响这次询问,每个人的兴奋值会因为这次询问变成 \(min(a_i,i−l+1)\) 。
思路一
考虑每个点的贡献情况,只有当 \(a_i < i - l+1\) 时 \(a_i\) 才有贡献,答案的值域就被限制在 \([1,r - l+1]\) 内。
注意到 答案的上界具有单调性 。我们考虑当确定一个最大值 \(max\) 时,\(max\) 总可以表示为 \(i-l+1\) 的形式,以 \(i\) 为分界,\([l,i]\) 的区间不可能有更优值(上界的单调性),对 \([i+1,r]\) ,如果区间最大值比 \(max\) 大,那么选取更大肯定更优。
因此st表二分答案即可。
这个思路感觉没见过,这里虽然 \(a_i\) 不具有单调性,但是 \(i-l+1\) 有单调性并且其还是答案上界,每一个可能解都可以映射到一个 \(i-l+1\) 上面。进一步的发现可以通过 \(i\) 分割区间,得到很好的二分性质。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
void solve()
{
int n,m; cin >> n >> m;
vector<int> a(n + 1);
rep(i,1,n) cin >> a[i];
vector<vector<int>> f(n + 1,vector<int>(21));
for(int i = 1;i <= n;i += 1) f[i][0] = a[i];
for(int j = 1;j <= 20;j += 1) {
for(int i = 1;i + (1 << j) - 1 <= n;i += 1) {
f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
}
auto getMax = [&](int l,int r) {
if(l > r) return -1ll * linf;
int k = __lg(r - l + 1);
return max(f[l][k], f[r - (1 << k) + 1][k]);
};
auto query = [&](int ql,int qr) {
// [l,r] i - l + 1 [i,r]
int l = ql - 1,r = qr + 1;
auto chk = [&](int pos) {
return pos - ql + 1 <= getMax(pos,qr);
};
while(l + 1 < r) {
int mid = l + r >> 1;
if(chk(mid)) l = mid;
else r = mid;
}
return l - ql + 1;
};
while(m -- ) {
int l,r; cin >> l >> r;
cout << query(l,r) << ' ';
}
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//int T;cin>>T;
//while(T--)
solve();
return 0;
}
思路二
考虑每一个点的贡献,可以发现当 \(l \ge i+1-a_i\) 时贡献来自下标 \(i\) 。并不会因为 \(l\) 的增大变回 \(a_i\) 。因此我们也可以考虑按 \(l\) 从小到大依次处理询问。
考虑如何维护每个点的贡献。当达到边界条件 \(l = i+1-a_i\) 时应该切换贡献。对等式右边,我们可以开桶把值为 \(i+1-a_i\) 的下标放一起。之后处理询问时枚举所有 \(l\) ,当达到对应边界条件,更新这些点。
这个思路可以实现的关键还是在于不等式的单调性和询问区间 \(l\) 和变化条件有关。当枚举 \(i+1-a_i\) 时就相当于对询问区间的枚举。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 1e5 ,mod=1e9 + 7;
int n,q;
int a[MAXN];
struct SegTree {
#define TRMAXN 100010
#define lc (p << 1)
#define rc (p << 1 | 1)
struct SNode {
int l,r;
// TODO: 定义需要维护的数据
int mxv,mxl;
// TODO: 设置tag
int tagV,tagL;
SNode() {
l = r = 0;
// TODO: 数据构造
mxv = mxl = -inf;
tagV = tagL = -inf;
}
void init(int x) {
l = r = x;
// TODO: 单点初始化数据
mxv = a[x];
mxl = -inf;
tagV = tagL = -inf;
}
SNode operator+ (const SNode& rv) const {
SNode t;
// [l,r]
t.l = l, t.r = rv.r;
// TODO: 左右儿子合并操作+
t.mxv = max(mxv, rv.mxv);
t.mxl = max(mxl, rv.mxl);
return t;
}
}tr[TRMAXN << 2];
void pushup(int p) {
tr[p] = tr[lc] + tr[rc];
}
void upd(int p,int val) {
// 数据更新
tr[p].mxv = -inf;
tr[p].mxl = val;
// 标签修
}
void pushdown(int p) { // 标签下放
}
// 下面的大多数时候不用改
void build(int l,int r,int p = 1) {
if(l == r) return tr[p].init(l);
int mid = l + r >> 1;
build(l,mid,lc);
build(mid + 1,r,rc);
pushup(p);
}
void modify(int l,int r,int val,int p = 1) {
int L = tr[p].l, R = tr[p].r;
if(l <= L && R <= r) {
upd(p,val);
return;
}
pushdown(p);
int mid = L + R >> 1;
if(l <= mid) modify(l,r,val,lc);
if(r > mid) modify(l,r,val,rc);
pushup(p);
}
void modify(int tar,int val) {return modify(tar,tar,val);}
SNode query(int l, int r,int p = 1) {
int L = tr[p].l, R = tr[p].r;
if(l <= L && R <= r) return tr[p];
pushdown(p);
int mid = L + R >> 1;
if(l <= mid && r > mid) return query(l,r,lc) + query(l,r,rc);
if(l <= mid) return query(l,r,lc);
return query(l,r,rc);
}
SNode query(int tar) {return query(tar,tar);}
}tr;
void solve()
{
cin >> n >> q;
for(int i = 1;i <= n;i += 1) cin >> a[i];
vector<vector<int>> b(n + 1);
for(int i = 1;i <= n;i += 1) b[max(0ll, i + 1 - a[i])].push_back(i);
tr.build(1,n);
vector<vector<PII>> qs(n + 1);
for(int i = 1;i <= q;i += 1) {
int l,r; cin >> l >> r;
qs[l].push_back({r,i});
}
vector<int> ans(q + 1);
for(int i = 0;i <= n;i += 1) {
for(auto pos : b[i]) tr.modify(pos,pos);
for(auto [j,id] : qs[i]) {
auto res = tr.query(i,j);
ans[id] = max(res.mxl - i + 1, res.mxv);
}
}
for(int i = 1;i <= q;i += 1) cout << ans[i] << " ";
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//int T;cin>>T;
//while(T--)
solve();
return 0;
}