HDU - 6286 CCPC 2018湖南邀请赛《2018》题解

链接:https://vjudge.net/problem/HDU-6286

题意:

给两段区间,问从这两段区间内各选出一个数,能组成多少个有序数对,使得数对里的两个元素乘积为2018的倍数。

思路:

首先注意两点,区间范围是1 ~ 1e9,n^2循环是不存在的了,而本题只能使用O(1)的复杂度;

其次要注意,<2018, 1> 和 <1, 2018> 显然是两个不同的有序对。

接下来讲下大体思路,刚才提到这题正解是O(1)的,那就说明肯定存在某些规律来算答案。

首先,2018这个数,Emmm,其实挺不错的,因为它就4个因子:1,2,1009,2018。

所以我的想法是,先看最大的因子所能决定的有序对个数,找下这两段区间分别有多少数是2018的倍数,然后再用其中一个区间2018倍数的个数乘另一个区间的长度再加和即可,即:

ll sum = 0;
//区间1内2018倍数个数
ll a1 = r / 2018 - l / 2018;
if(l % 2018 == 0) a1++;
//区间2内2018倍数个数
ll a2 = R / 2018 - L / 2018;

if(L % 2018 == 0) a2++;

//ps: 这里要提一下,在某段区间 [l, r] 内找某个数x的倍数个数tmp,不需要循环枚举,只需要两步:

int  tmp =  r / x - l / x;    if(l % x == 0) tmp++;

sum += a1 * (R - L + 1) + a2 * (r - l + 1);

但是要注意这么做的话会有重复的情况,那就是再乘区间长的的时候,把之前找过的2018的倍数又覆盖到了,举个栗子~

第一个区间是1 ~ 2018,故第一个区间内2018倍数个数是1,即只有2018,第二个区间是 1~ 4036,故第二个区间内2018倍数个数是2,即2018和4036,而第一个区间找到的2018在乘区间长度的时候,会覆盖到2018(1) * 2018(2) 和 2018(1) * 4036(2),为了方便理解,这里加的括号(1)  (2)表示属于哪段区间。所以此时,sum应减去这个重复值,即sum -= a1 * a2。

然后开始找1009的奇数倍,因为2018的倍数找过了,而2018的倍数都是1009的偶数倍,为避免重复,只需要找一段区间内1009的奇数倍再乘另一段区间内除了2018之外的偶数个数,在累加到sum上即可。而1009奇数倍的求法就用1009的倍数个数减去2018的倍数个数即可。

即进行如下操作:

//区间1内1009倍数个数
ll c1 = r / 1009 - l / 1009;
if(l % 1009 == 0) c1++;
//区间2内1009倍数个数

ll c2 = R / 1009 - L / 1009;

if(L % 1009 == 0) c2++;

ll t1 = c1 - a1; //区间1内1009奇数倍的个数
ll t2 = c2 - a2; //区间2内1009奇数倍数个数
//区间1内偶数个数
ll p1 = r / 2 - l / 2;
if(!(l & 1)) p1++;
//区间2内偶数个数
ll p2 = R / 2 - L / 2;
if(!(L & 1)) p2++;
sum += t1 * (p2 - a2) + t2 * (p1 - a1); //该区间1009奇数倍数个数 * 另一区间除2018倍数外偶数个数


本人AC代码://代码注释也很详细哦 ~

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <queue>
#include <set>
#include <map>
#include <ctime>
#include <cctype>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int Inf = 1e9 + 7;
const double PI  = acos(-1.0);
queue <int> qua;
map <int, int> mp;

priority_queue < int, vector<int>, less<int> > qua1;
priority_queue < int, vector<int>, greater<int> > qua2;
ll l, r, L, R;

int main() {
    while(scanf("%lld %lld %lld %lld", &l, &r, &L, &R) != EOF) {
        ll sum = 0;

        //区间1内2018倍数个数
        ll a1 = r / 2018 - l / 2018;
        if(l % 2018 == 0) a1++;

        //区间2内2018倍数个数
        ll a2 = R / 2018 - L / 2018;
        if(L % 2018 == 0) a2++;

        sum += a1 * (R - L + 1) + a2 * (r - l + 1) - a1 * a2; //减掉a1 * a2 是排除2018倍数 * 2018倍数重复情况

        //区间1内1009倍数个数
        ll c1 = r / 1009 - l / 1009;
        if(l % 1009 == 0) c1++;

        //区间2内1009倍数个数
        ll c2 = R / 1009 - L / 1009;
        if(L % 1009 == 0) c2++;

        ll t1 = c1 - a1; //区间1内1009奇数倍的个数
        ll t2 = c2 - a2; //区间2内1009奇数倍数个数

        //区间1内偶数个数
        ll p1 = r / 2 - l / 2;
        if(!(l & 1)) p1++;

        //区间2内偶数个数
        ll p2 = R / 2 - L / 2;
        if(!(L & 1)) p2++;

        sum += t1 * (p2 - a2) + t2 * (p1 - a1); //该区间1009奇数倍数个数 * 另一区间除2018倍数外偶数个数

        cout << sum << endl;
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值