传送门:HDU5875
题意:给定长度为n的一个序列和m次询问,每次询问给出l,r,求a[l] % a[l + 1] % a[l + 2] ... %a[r]。
思路:我们只单纯考虑有效取模的话,那么一个数x最多取模logx次,那么问题是怎么使得每次取模操作都有效,转化为找到一个区间内第一个比x小的数,线段树可以解决,要注意的是用线段树的话常数大,很容易T,可以加一个小优化:当要寻找的区间包含目前结点对应的区间时,我们可以转化为类似二分查找的方式,避免无用的递归,详见:点击打开链接
寻找一个区间内第一个比x小的数也可以ST表 + 二分,ST表预处理区间最小值,然后二分区间长度。详见:点击打开链接
ST表 + 二分是n(logn)^2的复杂度,但即使这样也比不加优化的线段树快。。可想而知线段树常数有多大。
最后要说的是这题最快的解法,那就是暴力,单调栈对于每一个位置预处理出后面数值比它小的第一个位置,然后跳着取模,只需要600+ms,神他妈的随便构造一组递减的样例就能hack掉这种暴力,然而谁让官方数据没有呢。
代码(没加上面说的优化):
#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
using namespace std;
typedef pair<int,int> P;
const int MAXN = 100010;
int num[MAXN << 2], a[MAXN];
void build(int l, int r, int rt)
{
if(l == r)
{
scanf("%d", a + l);
num[rt] = a[l];
return ;
}
int mid = (l + r) >> 1;
build(lson);
build(rson);
num[rt] = min(num[rt << 1], num[rt << 1 | 1]);
}
int query(int L, int R, int aim, int l, int r, int rt)
{
if(num[rt] > aim) return r + 1;
if(l == r)
return l;
int mid = (l + r) >> 1, ans = r + 1;
if(L <= mid) ans = query(L, R, aim, lson);
if(ans <= r) return ans;
if(R > mid) ans = min(ans, query(L, R, aim, rson));
return ans;
}
int main()
{
int T, n, m, l, r, ans;
cin >> T;
while(T--)
{
scanf("%d", &n);
build(1, n, 1);
scanf("%d", &m);
while(m--)
{
scanf("%d %d", &l, &r);
ans = a[l++];
while(l <= r && ans)
{
l = query(l, r, ans, 1, n, 1);
if(l <= r) ans %= a[l];
l++;
}
cout << ans << endl;
}
}
return 0;
}