问题 A: 模(mod)
时间限制: 1 Sec 内存限制: 128 MB
题目描述
王小花喜欢模法,所以她生成了一个序列,没事干的时候模着玩.
王小花的序列a是一个长度为n的正整数序列。王小花每次会指定一个非负整数x和一个位置区间[l,r],并希望求出的值x mod a[l] mod a[l+1] ⋯ mod a[r−1] mod a[r]。
因为王小花知道暴力模不可取,所以她想让你帮忙用计算机求出答案。
输入
第一行输入两个正整数n,m,其中m是询问次数。
第二行输入n个正整数,第i个数表示a[i]。
接下来m行,每行输入三个整数x,l,r。
输出
对于每个询问,输出一行一个整数ans表示答案。
样例输入 Copy
4 2
4 3 9 6
7 1 2
777 3 4
样例输出 Copy
0
3
提示
对于100%的数据,n,m≤105
思路:
根据题意知道暴力不可取,所以思考优化一下。
仔细看这个式子:x mod a[l] mod a[l+1] ⋯ mod a[r−1] mod a[r]
暴力是要取模很多次,但实际上并非如此,我们可以算出一个数经过一次取模,他的大小最少会衰减一半。如 :
y=x+mod;
得x<mod;
得y>2*x;
好了,所以我们发现一个数w最多衰减log2 (w) 次。然后我们就要想出怎么找到从当前指针k到r这一段区间里第一个比x小的数就行了,然后怎么实现呢,考虑 st表上二分。
对于st表来说,固定左端点后,查询f[L][x],这个值随x得增加是有单调性的,然后就可以二分了,二分不太好写,我写的比较复杂,还用了递归,就常数比较大,要卡一下常数。
详细细节 见代码:
#pragma GCC optimize(2)
#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 400004;
int f[maxn][20];
int a[maxn];
int n,q;
void st()
{
for(int i = 1; i <= n; i ++) f[i][0] = a[i];
int t = log(n) / log(2) + 1;
for(int j = 1; j < 20; j ++){
for(int i = 1; i <= n - (1 << j) + 1; i ++){
f[i][j] = min(f[i][j-1],f[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int x, int y)
{
int t = log(abs(y-x + 1))/ log(2);
int a = f[x][t];
int b = f[y - (1 << t) + 1][t];
return min(a,b);
}
void solve(int x,int L,int R)
{
int l,r,mid;
l=L; r=R;
if(l>r){
printf("%d\n",x); return ;
}
int op=query(l,r);
if(op>x) //如果[l,r]中所有的数都比x大,直接输出x。
{
printf("%d\n",x); return ;
}
while(l<=r){
mid=(l+r)/2;
op=query(L,mid);
if(op>x)
l=mid+1;
else
r=mid-1;
}
l-=10; l=max(L,l);
for(int i=l;i<=R;i++){
if(a[mid]<=x){
mid=i; break;
}
}
/// 在区间[l,r]中找到第一个 小于等于 x的数的下标 , 此处用st表上二分实现,
x%=a[mid]; mid++;
solve(x,mid,R);
}
int main()
{
int m,x,l,r;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
st();
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&l,&r);
x=x%a[l];
solve(x,l,r);
}
return 0;
}