[Luogu P3172] [BZOJ 3930] [CQOI2015]选数

洛谷传送门

BZOJ传送门

题目描述

我们知道,从区间 [ L , H ] [L,H] [L,H] L L L H H H为整数)中选取 N N N个整数,总共有 ( H − L + 1 ) N (H-L+1)^N (HL+1)N种方案。小z很好奇这样选出的数的最大公约数的规律,他决定对每种方案选出的 N N N个整数都求一次最大公约数,以便进一步研究。然而他很快发现工作量太大了,于是向你寻求帮助。你的任务很简单,小z会告诉你一个整数 K K K,你需要回答他最大公约数刚好为 K K K的选取方案有多少个。由于方案数较大,你只需要输出其除以 1000000007 1000000007 1000000007的余数即可。

输入输出格式

输入格式:

输入一行,包含 4 4 4个空格分开的正整数,依次为 N N N K K K L L L H H H

输出格式:

输出一个整数,为所求方案数。

输入输出样例

输入样例#1:

2 2 2 4

输出样例#1:

3

说明

样例解释

所有可能的选择方案: ( 2 , 2 ) , ( 2 , 3 ) , ( 2 , 4 ) , ( 3 , 2 ) , ( 3 , 3 ) , ( 3 , 4 ) , ( 4 , 2 ) , ( 4 , 3 ) , ( 4 , 4 ) (2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4) (2,2),(2,3),(2,4),(3,2),(3,3),(3,4),(4,2),(4,3),(4,4)

其中最大公约数等于 2 2 2的只有 3 3 3组: ( 2 , 2 ) , ( 2 , 4 ) , ( 4 , 2 ) (2, 2), (2, 4), (4, 2) (2,2),(2,4),(4,2)

对于100%的数据, 1 ≤ N , K ≤ 1 0 9 1\le N,K\le 10^9 1N,K109 1 ≤ L ≤ H ≤ 1 0 9 1\le L\le H\le 10^9 1LH109 H − L ≤ 1 0 5 H-L\le 10^5 HL105

解题分析

首先还是很套路地将 L L L变成 ⌈ L K ⌉ \lceil \frac{L}{K}\rceil KL H H H变成 ⌊ H K ⌋ \lfloor\frac{H}{K}\rfloor KH, 那么问题就变成了从 [ L , H ] [L,H] [LH]选出 N N N个数, 使得其 g c d gcd gcd 1 1 1

这里有个很讨厌的问题: L , H L,H L,H可能很大, 但 H − L H-L HL比较小。 如果我们直接枚举包含约数 i i i的方案数个数, 很可能需要枚举到 1 0 9 10^9 109, 这显然是不可行的。 然后我们发现, 这些情况都只会取 N N N次一个数, 因为如果在 [ L , H ] [L,H] [L,H]中取多个数的话, 它们的 g c d gcd gcd不会超过 1 0 5 10^5 105, 而这种约数对我们的答案是没有贡献的。

因此, 我们先不考虑只有一种数的情况, 那么我们包含的约数 i i i只需要枚举到 H − L H-L HL。 然后这样的方案数为 c n t i N − c n t i cnt_i^N-cnt_i cntiNcnti。 最后从大到小容斥一遍即可。

如果 L = 1 L=1 L=1, 那么实际上我们是可以全部取到 K K K的, 最后方案数 + 1 +1 +1即可。

代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
#define MX 100500
#define MOD 1000000007ll
int dp[MX];
IN int fpow(R int base, R int tim)
{
    int ret = 1;
    W (tim)
    {
        if (tim & 1) ret = 1ll * ret * base % MOD;
        base = 1ll * base * base % MOD, tim >>= 1;
    }
    return ret;
}
int main(void)
{
    int n, l, h, k, halt, lef, rig;
    scanf("%d%d%d%d", &n, &k, &l, &h);
    l = std::ceil(1.0 * l / k), h = h / k;
    halt = h - l;
    if (l > h) return puts("0"), 0;
    for (R int i = 1; i <= halt; ++i)
    {
        lef = std::ceil(1.0 * l / i), rig = h / i;
        if (lef > rig) continue;
        dp[i] = (fpow((rig - lef + 1), n) - (rig - lef + 1) + MOD) % MOD;
    }
    for (R int i = halt; i; --i)
    {
        for (R int j = i << 1; j <= halt; j += i)
        dp[i] = (dp[i] - dp[j] + MOD) % MOD;
    }
    if (l == 1) dp[1] = (dp[1] + 1) % MOD;
    printf("%d", dp[1]);
}

你可能感兴趣的:(数学,容斥)