JZOJ ???? 咕咕

          • 没有传送门
          • 题目大意
          • 考场上的思路
          • 思路
          • 参考代码
          • 总结
          • Remark

没有传送门
题目大意

n n 种物品,求完全背包方案数总和,背包容量从 L L R R 。设物品的体积分别为 ai a i

n10 n ≤ 10 ai105 ∏ a i ≤ 10 5 1LR1017 1 ≤ L ≤ R ≤ 10 17

Limited Constraint: O(nR) O ( n R ) 能过。

考场上的思路

暴力……

注意到 n n ​ 很小, a a ​ 的乘积有限,因此考虑循环节,然而什么都没有看出来,还白白浪费了我三十分钟。

思路

注意到完全背包方案数可以等价于求以下方程的非负整数解的个数:

c1a1+c2a2++cnan=V c 1 a 1 + c 2 a 2 + ⋯ + c n a n = V

把问题转换成前缀相减,则我们要求的是 V[0,R] V ∈ [ 0 , R ] 的解的个数。

注意到 ai a i 的乘积比较小,显然这意味着 lcm(a1an) l c m ( a 1 ∼ a n ) 比较小。考虑下面的方程:

(d1lcm{a}a1+c1)a1+(d2lcm{a}a2+c2)a2++(dnlcm{a}an+cn)an=V(V[0,R]) ( d 1 l c m { a } a 1 + c 1 ) a 1 + ( d 2 l c m { a } a 2 + c 2 ) a 2 + ⋯ + ( d n l c m { a } a n + c n ) a n = V ( V ∈ [ 0 , R ] )

如果我们强制要求 ciai<lcm{a} c i a i < l c m { a } ,那么显然 c c d d 的非负整数解个数等于原来的 c c 的非负整数解个数。考虑枚举 d ∑ d 。对于每种 d ∑ d ,对答案的贡献 d d 的方案数乘以 c c 的方案数。

d d 确定时, c c 的取值相当于是做一个多重背包方案数,每种物品最多能够选 lcm{a}ai l c m { a } a i 个。这是可以 O(nlcm{a}) O ( n ⋅ l c m { a } ) 求出来的(见代码)。当 Rlcm{a}dlcm{a}n R − l c m { a } ⋅ ∑ d ≥ l c m { a } ⋅ n 时, c c 的总容量可以认为是 lcm{a}n l c m { a } ⋅ n ,而 d d 的取值为:

i=0Rlcm{a}nlcm{a}(i+n1n1) ∑ i = 0 ⌊ R − l c m { a } ⋅ n l c m { a } ⌋ ( i + n − 1 n − 1 )

=(n+Rlcm{a}nlcm{a}n) = ( n + ⌊ R − l c m { a } ⋅ n l c m { a } ⌋ n )

注意这里的下取整是数学意义的下取整,即当被除数为负数时向下取整而不是向零取整。对于剩下的部分,可以暴力枚举 d ∑ d 的取值,再用组合数来算。时间复杂度 O(nlcm{a}) O ( n ⋅ l c m { a } )

参考代码
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef LL INT_PUT;
INT_PUT readIn()
{
    INT_PUT a = 0; bool positive = true;
    char ch = getchar();
    while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
    if (ch == '-') { positive = false; ch = getchar(); }
    while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
    return positive ? -a : a;
}
void printOut(INT_PUT x)
{
    char buffer[20]; int length = 0;
    if (x < 0) putchar('-'); else x = -x;
    do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
    do putchar(buffer[--length]); while (length);
    putchar('\n');
}

const int mod = int(1e9) + 7;
const int maxn = 15;
int n;
int a[maxn];
LL L, R;
int gcd(int a, int b)
{
    return !b ? a : gcd(b, a % b);
}

LL power(LL x, int y)
{
    LL ret = 1;
    while (y)
    {
        if (y & 1) ret = ret * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return ret;
}

void add(int& x, const int& y)
{
    register int t;
    x = (t = x + y) >= mod ? t - mod : t;
}
void sub(int& x, const int& y)
{
    register int t;
    x = (t = x - y) < 0 ? t + mod : t;
}

#define RunInstance(x) delete new x
struct brute
{
    static const int maxm = int(1e5) + 5;
    int f[maxm];

    brute() : f()
    {
        f[0] = 1;
        for (int i = 1; i <= n; i++)
            for (int j = a[i]; j <= R; j++)
                f[j] = (f[j] + f[j - a[i]]) % mod;

        int ans = 0;
        for (int i = L; i <= R; i++)
            ans = (ans + f[i]) % mod;
        printOut(ans);
    }
};
struct work
{
    static const int maxm = int(1e5) + 5;
    int f[maxn * maxm];
    int lcm;
    int size;

    int C(LL down, LL up)
    {
        if (down < up) return 0;
        up = std::min(up, down - up);
        LL mul = 1;
        for (int i = 0; i < up; i++)
            mul = mul * ((down - i) % mod) % mod;
        LL div = 1;
        for (LL i = 2; i <= up; i++)
            div = div * i % mod;
        return mul * power(div, mod - 2) % mod;
    }

    int solve(LL x)
    {
        LL ret = 0;
        LL div = (x - size) / lcm;
        if (x - size >= 0) // note
        {
            ret = (LL)C(n + div, n) * f[size] % mod;
            div++;
        }
        else
            div = 0;
        for (LL i = div; lcm * i <= x; i++)
            ret = (ret + (LL)C(n - 1 + i, n - 1) * f[x - i * lcm]) % mod;
        return ret;
    }
    int temp[maxn * maxm];
    int contri[maxn];

    work() : f()
    {
        lcm = 1;
        for (int i = 1; i <= n; i++)
            lcm = lcm / gcd(lcm, a[i]) * a[i];
        size = lcm * n;

        f[0] = 1;
        for (int i = 1; i <= n; i++)
        {
            std::memset(temp, 0, sizeof(temp));
            for (int j = 0; j < a[i]; j++)
            {
                int contri = 0;
                for (int k = a[i] + j; k <= size; k += a[i])
                {
                    add(contri, f[k - a[i]]);
                    if (k - lcm >= 0)
                        sub(contri, f[k - lcm]);
                    add(temp[k], contri);
                }
            }
            for (int i = 0; i <= size; i++)
                add(f[i], temp[i]);
        }
        for (int i = 1; i <= size; i++)
            add(f[i], f[i - 1]);

        printOut((solve(R) - solve(L - 1) + mod) % mod);
    }
};

void run()
{
    n = readIn();
    for (int i = 1; i <= n; i++)
        a[i] = readIn();
    L = readIn();
    R = readIn();

    if (R <= LL(1e5))
        RunInstance(brute);
    else
        RunInstance(work);
}

int main()
{
#ifndef LOCAL
    freopen("gugu.in", "r", stdin);
    freopen("gugu.out", "w", stdout);
#endif
    run();
    return 0;
}
总结

不知道说什么……主要是把完全背包问题转化成方程非负解的个数的问题这个方法好像没有遇到过。

一直以为多重背包不能 O(nm) O ( n m ) 求方案数,原来可以……

Remark

用拉格朗日插值。

你可能感兴趣的:(OI,NOI,集训,数学)