nowcoder NC13221 数码(整除分块)

题目链接:https://ac.nowcoder.com/acm/problem/13221

题意:给定两个整数 l l l r r r ,对于所有满足 1 ≤ l ≤ x ≤ r ≤ 1 0 9 1 ≤ l ≤ x ≤ r ≤ 10^9 1lxr109 x x x ,把 x x x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求 1 ~ 9 1~9 19每个数码出现的次数。

思路:
在讲这一题得写法之前,我们先要引入整除分块这个知识。

整除分块是求 ∑ i = 1 n n / i \sum_{i=1}^{n}n/i i=1nn/i的和。
首先如果我们直接 f o r for for循环遍历一遍,那一般题目一定会超时。还是遵循遇事不决先打表的原则,我们将 n = 10 n=10 n=10带进去模拟一下,可以得到如下表所示的值:
在这里插入图片描述
我们可看到有很多的之都是一样的,那我们是不是就可以将所有 n / i n/i n/i相等的放在“一块”呢?那我们可以定义两个变量 l , r l,r l,r,用 l l l表示这一块的左区间, r r r表示这一块的右区间,所以 l l l我们要从一开始遍历,每次令 l = r + 1 l = r + 1 l=r+1,那 r r r呢?我们在草稿纸上算一下就知道, r = n / ( n / l ) r = n / (n / l) r=n/(n/l) n / l n/l n/l便是这个块内的值。所以这一块对答案的贡献就是 ( r − l + 1 ) ∗ ( n / l ) (r-l+1)*(n/l) (rl+1)(n/l)
具体代码实现如下:

for(int l = 1 , r ; l <= x ; l = r + 1) {
   	r = x / (x / i );
    ans += 1LL * (r - l + 1) * (x / l);
}

讲完了整除分块,我们回到原题。
题目是让我们求的数是 l , r l,r l,r这个区间的,假设函数 s o l v e ( x ) solve(x) solve(x)表示 [ 1 , x ] [1,x] [1,x]这个区间对应得答案,那么 [ l , r ] [l,r] [l,r]区间的答案是不是就是
s o l v e ( r ) − s o l v e ( l − 1 ) ! solve(r)-solve(l-1)! solve(r)solve(l1)那我们只要把这个 s o l v e ( x ) solve(x) solve(x)写出来不就好啦!!!可以该怎么写呢?
因为题目要求我们求 1 − > 9 1->9 1>9出现的个数,所以我们可以一个一个的算,拿 1 1 1举例,如果 x % y = = 0 x\%y == 0 x%y==0
其中 y ∈ [ 1 , 2 ) , [ 10 , 20 ) , [ 100 , 200 ) , [ 1000 , 2000 ) y\in[1,2),[10,20),[100,200),[1000,2000) y[1,2)[10,20)[100,200)[1000,2000)那答案对1的贡献++,那我们就可以细化到每个区间(用 l , r l,r l,r表示)。
[ 1 , x ] [1,x] [1,x]区间有多少个数可以整除 y y y呢?答案显然就是 x / y x/y x/y
那这一题就是让我们求 ( x / 1 ) + ( x / 10 + x / 11 + x / 12 + . . . ) (x/1) + (x/10 + x/11 + x/12 + ...) (x/1)+(x/10+x/11+x/12+...)这不就是我们上面讲的整除分块的板子题了嘛。

AC代码如下:

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <cmath>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

#define LL long long
#define pii pair<int,int>
#define sd(x) scanf("%d",&x)
#define slld(x) scanf("%lld",&x)
#define pd(x) printf("%d\n",x)
#define plld(x) printf("%lld\n",x)
#define rep(i,a,b) for(int i = a ; i <= b ; i++)
#define per(i,a,b) for(int i = b ; i >= a ; i--)
#define mem(a) memset(a,0,sizeof(a))
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define fast_io ios::sync_with_stdio(false)

const LL INF = 1e18;
const int mod = 2010;
const int maxn = 2e5 + 7;

LL solve(LL x,LL v) {
    LL res = 0;
    for(LL temp = 1 ; temp <= x / v ; temp *= 10) {
    	///确定左右区间
        LL l = temp * v , r = min(x , l + temp - 1);
        ///整除分块
        for(int i = l , j ; i <= r ; i = j + 1) {
            j = min(x / (x / i) , r);
            res += 1LL * (j - i + 1) * (x / i);
        }
    }
    return res;
}

int main() {
    int x,y;
    while(~scanf("%d%d",&x,&y)) {
        for(int i = 1 ; i <= 9 ; i++) {
        	///直接俄统计答案
            printf("%lld\n" , solve(y,i) - solve(x-1,i));
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值