n n 种物品,求完全背包方案数总和,背包容量从 L L 到 R R 。设物品的体积分别为 ai a i 。
n≤10 n ≤ 10 , ∏ai≤105 ∏ a i ≤ 10 5 , 1≤L≤R≤1017 1 ≤ L ≤ R ≤ 10 17 。
Limited Constraint: O(nR) O ( n R ) 能过。
暴力……
注意到 n n 很小, a a 的乘积有限,因此考虑循环节,然而什么都没有看出来,还白白浪费了我三十分钟。
注意到完全背包方案数可以等价于求以下方程的非负整数解的个数:
注意到 ai a i 的乘积比较小,显然这意味着 lcm(a1∼an) l c m ( a 1 ∼ a n ) 比较小。考虑下面的方程:
当 d d 确定时, c c 的取值相当于是做一个多重背包方案数,每种物品最多能够选 lcm{a}ai l c m { a } a i 个。这是可以 O(n⋅lcm{a}) O ( n ⋅ l c m { a } ) 求出来的(见代码)。当 R−lcm{a}⋅∑d≥lcm{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 的取值为:
注意这里的下取整是数学意义的下取整,即当被除数为负数时向下取整而不是向零取整。对于剩下的部分,可以暴力枚举 ∑d ∑ d 的取值,再用组合数来算。时间复杂度 O(n⋅lcm{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 ) 求方案数,原来可以……
用拉格朗日插值。