ABC147-F-数论,思维

题目大意:

给你一个等差数列 a i = x + i ∗ d a_i=x+i*d ai=x+id。然后你可以从里面选出一个子集 S S S(包含0和全集)。问你能够得到多少不同的结果.

n ≤ 1 e 5 , x , d ≤ 1 e 8 n \leq 1e5,x,d\leq1e8 n1e5,x,d1e8

题目思路:

首先,特判 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=kx+cd. k x kx kx定值,那么c的范围一定是: 0 + . . . + k − 1 ≤ c ≤ n − k + . . . + n − 1 0+...+k-1\leq c\leq n-k+...+n-1 0+...+k1cnk+...+n1并且连续.

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) kixkjx (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+jdn.这些数一定在一个同余类中。但是这样并不一定全面.因为有可能存在 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里一个线段?但是随机数据下应该不会跑满,说不定还很小.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值