题目描述
给你整数 N,X 和长度为 N 的正整数序列 A=(A \_ 1,A \_ 2,\\ldots,A \_ N) 。
对于一个非负整数 x ,定义 f(x)=(\\ldots((x \\bmod A \_ 1) \\bmod A \_ 2) \\ldots ) \\bmod A \_ N 。
求介于 1 与 X 之间的整数 x 的个数,使得 f(x)=0 .
给你 T 个测试用例,请逐个求解。
数据范围
- 1\\le T\\le 2\\times 10\^5
- 1\\le N\\le 2\\times 10\^5
- 所有测试用例中 N 的总和最多为 2\\times 10\^5 。
- 1\\le X\\le 10\^{18}
- 1\\le A_i\\le 10\^{18}
- 所有输入值均为整数。
解题思路
核心思想
这个问题要求统计在 [1, X] 范围内,经过一系列取模操作后结果为 0 的数的个数。
关键观察:取模操作具有"分段常数"性质 。对于给定的模数 v,所有数 x 在区间 [kv, (k+1)v - 1] 内取模 v 的结果相同,都是 x - kv。
算法设计
用一个**优先队列(最大堆)**来维护区间信息,每个元素表示一个"状态区间":
-
val:当前状态值(取模后的结果) -
cnt:有多少个原始数x能达到这个状态
具体步骤
-
初始化:
pq.push({x + 1, 1}); // 表示原始区间 [0, X] 内有 X+1 个数,状态值为 X+1 -
逐个处理模数 :
对每个
v = A[i],将当前所有状态val对v取模:-
从堆中取出最大的
val(因为我们需要处理所有相同的val) -
合并所有相同
val的计数 -
分裂成新状态:
-
{v, val / v * cnt}:表示能整除的部分,取模后结果为 0 -
{val % v, cnt}:表示余数部分(如果余数不为 0)
-
-
-
统计答案 :
最终堆中所有
val = 0的状态计数之和,就是f(x) = 0的x的数量。由于统计的是
[0, X]区间(包含 0),而题目要求[1, X],所以答案为总计数 - 1(减去x = 0的情况)。
完整代码
#include<bits/stdc++.h>
#define fr1(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define fr2(i,a,b) for(int (i)=(a);(i)>=(b);(i)--)
#define fv(i,p) for(auto (i):(p))
#define ll long long
#define ull unsigned ll
#define pii pair<int,int>
#define pll pair<ll,ll>
#define _1st first
#define _2nd second
#define elif else if
#define debug cout<<endl<<"-------------------------------------------------------------"<<endl
using namespace std;
void solve() {
int n;
ll x;
cin >> n >> x;
vector<ll> a(n);
for (ll &v : a) cin >> v;
priority_queue<pll> pq;
pq.push({x + 1, 1});
for (ll v : a) {
while (!pq.empty() && pq.top()._1st > v) {
auto [val, cnt] = pq.top();
pq.pop();
while (!pq.empty() && pq.top()._1st == val) {
cnt += pq.top()._2nd;
pq.pop();
}
pq.push({v, val / v * cnt});
if (val % v != 0) pq.push({val % v, cnt});
}
}
ll ans = -1;
while (!pq.empty()) {
ans += pq.top()._2nd;
pq.pop();
}
cout << ans << '\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin >> t;
while (t--) solve();
return 0;
}