题目大意:
给你一个等差数列 a i = x + i ∗ d a_i=x+i*d ai=x+i∗d。然后你可以从里面选出一个子集 S S S(包含0和全集)。问你能够得到多少不同的结果.
n ≤ 1 e 5 , x , d ≤ 1 e 8 n \leq 1e5,x,d\leq1e8 n≤1e5,x,d≤1e8
题目思路:
首先,特判 d = 0 d=0 d=0.若 x = 0 x=0 x=0.则答案为0.否则答案为 n + 1 n+1 n+1.
1.考虑选择了 k k k个元素出来。那么 s u m = k ∗ x + c ∗ d sum=k*x +c*d sum=k∗x+c∗d. k x kx kx定值,那么c的范围一定是: 0 + . . . + k − 1 ≤ c ≤ n − k + . . . + n − 1 0+...+k-1\leq c\leq n-k+...+n-1 0+...+k−1≤c≤n−k+...+n−1并且连续.
2.映射到坐标轴上,就是位于直线 y = k x + b y=kx+b y=kx+b的离散点。我们的目的就是要统计这些离散点的个数.这里用到了一个技巧:
由于点之间严格相差 d d d.所以我们将其 / d /d /d之后,他们的增量变为1.那这就变成了一条线段。求出左右两个端点即可求出点的个数。
3.1 而且可以发现,对于一些 k k k来讲,他们的点会发生重合。例如:
x
=
3
,
d
=
2
x =3,d=2
x=3,d=2. 那么
k
=
1
时
k=1时
k=1时点为:3 5 7 9 11 13 15…
k
=
3
时
k=3时
k=3时点为:15 17…
两个在15处重合,往后的都一样了.
3.2 并且对于一些 k k k来讲,他们的点永远不会发生重合。起点不重合,且差值相同。那么永远不会相遇。
具体的,当 k i ∗ x ≡ k j ∗ x ( m o d d ) k_i*x \equiv k_j*x \ (mod \ d) ki∗x≡kj∗x (mod d)时,他们有点会重合.那么对于在一个同余类的 k k k.我们统一处理, / d /d /d之后变线段,丢到set里求线段并即可.
4.但是d太大了,这里需要进一步优化。
有两种解决方法:
方法一:
直观的,枚举
k
i
∈
[
0
,
d
)
k_i \in[0,d)
ki∈[0,d),然后再枚举
k
i
+
j
d
≤
n
k_i+jd \leq n
ki+jd≤n.这些数一定在一个同余类中。但是这样并不一定全面.因为有可能存在
k
d
≠
k
i
+
j
d
k_d \neq k_i+jd
kd=ki+jd的
k
d
k_d
kd使得他们两个也在同一个同余类中。本质就是因为
x
,
d
x,d
x,d不互质.
但是我们只要将
x
,
d
x,d
x,d除以
g
c
d
gcd
gcd.将其转化为互质的。这样以来。就是以
k
k
k为同余类了.然后就很显然了.具体细节见代码.
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
int main()
{
ios::sync_with_stdio(false);
ll n , x , d; cin >> n >> x >> d;
if (d == 0){
if (x == 0) cout << 1 << endl;
else {
cout << n + 1 << endl;
}
return 0;
}
ll gcd = __gcd(x , d);
x /= gcd; d /= gcd;
if (d < 0) {
x = -x; d = -d;
}
ll ans = 0;
for (ll i = 0 ; i < d ; i++){
set<pair<ll,ll>>s;
for (ll j = i ; j <= n ; j += d){
ll tmp = x * j;
ll l = (0+j-1)*j/2 , r = (n-1+n-j)*j/2;
s.insert(mp(tmp/d + l , tmp/d + r));
}
ll mi , mx;
mi = mx = -1e17;
mx--;
for (auto g : s){
if (g.first > mx){
ans += mx - mi + 1;
mi = g.first;
mx = g.second;
}else {
mx = max (mx , g.second);
}
}
ans += mx - mi + 1;
}
cout << ans << endl;
return 0;
}
方法二:(未验证)
直接枚举 k ∈ [ 0 , n ] k \in [0,n] k∈[0,n].利用 m o d d mod \ d mod d归桶。然后再做 s e t set set的线段合并。可以map套set或者离散化余数再set吧?
最多是开 1 e 5 1e5 1e5个set,每个set里一个线段?但是随机数据下应该不会跑满,说不定还很小.