gym101201J Shopping 二分+RMQ+数学性质

题目传送门

题目大意:

  给出n个商品的价格,排成一列,q次查询,每次查询如果你有x的钱,从l格子走到r格子,每种商品有无数个,能买就买,最后还会剩多少钱。

思路:

  每一次买都要找离自己最近的且买的起的商品,这样可以二分区间,用线段树(rmq问题,可以用st表)找到离自己最近且买得起的商品,然后不断的向r逼近,最后就是答案。

  这个思路为什么不会超时的呢,因为可以想象,每次买完一个商品,你的剩余的钱最多也是这个商品价格的余数,而后面你买的起的商品价格肯定比这个小,所以稍微举几个例子就发现不会枚举几次的,有点像斐波那契数列的递减。(最坏的情况是商品价格为7,6,5,4,3,2,1,这样的时间复杂度也许会退化,但发现如果能走完这个过程,至少也要有28块钱,这个钱在买7的时候会直接用完,所以时间复杂度不会退化)。

  借用了队友写的代码,st表解决RMQ真的好短。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010;
int n, q;
ll a[N];

ll dp[N][20];
int mm[N];
void init(int n, ll b[])
{
    mm[0] = -1;
    for (int i = 1; i <= n; ++i)
    {
        mm[i] = ((i & (i - 1)) == 0) ? mm[i - 1] + 1: mm[i - 1];
        dp[i][0] = b[i];
    }
    for (int j = 1; j <= mm[n]; ++j)
        for (int i = 1; i + (1 << j) - 1 <= n; ++i)
            dp[i][j] = min(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}

ll query(int l, int r)
{
    int k = mm[r - l + 1];
    return min(dp[l][k], dp[r - (1 << k) + 1][k]);
}

int main()
{
    while (scanf("%d%d", &n, &q) != EOF)
    {
        for (int i = 1; i <= n; ++i) scanf("%lld", a + i); init(n, a); 
        ll v;
        for (int i = 1, l, r; i <= q; ++i)
        {
            scanf("%lld%d%d", &v, &l, &r);
            while (r - l >= 0)
            {
                int ql = l, qr = r, tar = -1;
                while (qr - ql >= 0)
                {
                    int mid = (ql + qr) >> 1;
                    if (query(ql, mid) <= v)
                    {
                        qr = mid - 1;
                        tar = mid;
                    }
                    else
                        ql = mid + 1;
                }
                if (tar == -1) break;
                v %= a[tar];
                l = tar + 1;
            }
            printf("%lld
", v);
        }
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/mountaink/p/10094031.html