Problem
Pacific Northwest Region Programming Contest Division 1
https://vjudge.net/contest/188571#problem/J
Meaning
有 n 种物品,编号 1 ~ n,每种 ai 元。有 q 个人,初始时有 vi 元,要按顺序买 [li,ri] 的物品,每遇到一种物品,都是能买多少就买多少,问最后每个人都剩多少钱。
Analysis
朴素的想法,就是每个人都扫一遍他的购买区间,每遇到一种就对价格求余一次,最后剩下的就是答案。
但范围很大,不能暴力。又知道对一个比自己大的数求余是没有任何实际效果的,所以就每次都从区间中找到第一个价格比当前剩余的钱数小的那个物品,买它,然后把区间的左端点更新为该物品的位置。直到区间长度为零,或区间中找不到这样的物品为止。
找区间中第一个比它小的数,可以用线段树记录区间最小值,每次都优先往左找,不行再往下找。注意要保证找到的位置在查询区间内。
还有过另一种想法:用单调栈处理出每种物品的后面第一个价格低于它的物品的位置,在扫区间的时候不暴力扫,而是跳着扫。这样写能过这道题,而且用时更少…但这样在价格序列是降序或基本都是降序的时候没有任何优化效果,故应该不是正解。
Code
正解版
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200000;
long long tree[N<<2|3];
inline void pushup(int t)
{
tree[t] = min(tree[t<<1], tree[t<<1|1]);
}
int query(long long v, int ql, int qr, int l, int r, int rt)
{
// 已是叶子
if(l == r)
return tree[rt] > v ? -1 : l;
// 完全在查询区间内
// 如果 tree[rt] <= v
// 则还要往下找
if(ql <= l && r <= qr && tree[rt] > v)
return -1;
int m = l + r >> 1, pos = -1;
// 优先考虑往左找
if(ql <= m)
pos = query(v, ql, qr, l, m, rt<<1);
// 左边找不到再考虑往右找
if(pos == -1 && qr > m)
pos = query(v, ql, qr, m + 1, r, rt<<1|1);
return pos;
}
long long a[N+1];
void build(int l, int r, int rt)
{
if(l == r)
{
tree[rt] = a[l];
return;
}
int m = l + r >> 1;
build(l, m, rt<<1);
build(m + 1, r, rt<<1|1);
pushup(rt);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n, q;
cin >> n >> q;
for(int i = 1; i <= n; ++i)
cin >> a[i];
build(1, n, 1);
long long v;
for(int l, r; q--; )
{
cin >> v >> l >> r;
for(int p; v > 0 && l <= r; l = p + 1)
{
p = query(v, l, r, 1, n, 1);
if(p < 0) // 找不到
break;
v %= a[p];
}
cout << v << '\n';
}
return 0;
}
单调栈非正解版
#include <iostream>
using namespace std;
const int N = 200000;
long long a[N+11];
int nxt[N+11];
void make_next(int n)
{
static int stk[N+1];
int t = 0;
for(int i = 1; i <= n; ++i)
{
while(t > 0 && a[stk[t-1]] > a[i])
{
nxt[stk[t-1]] = i;
--t;
}
stk[t++] = i;
}
while(t-- > 0)
nxt[stk[t]] = n + 1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n, q;
cin >> n >> q;
for(int i = 1; i <= n; ++i)
cin >> a[i];
make_next(n);
for(int l, r; q--; )
{
long long v;
cin >> v >> l >> r;
while(l <= r && a[l] > v)
++l;
for( ; v > 0 && l <= r; l = nxt[l])
v %= a[l];
cout << v << '\n';
}
return 0;
}