统计理想数组的数目(GOOD)
- 划分解空间:枚举子数组最后一个数字然后累加
- 最关键的步骤:利用分解质因数枚举后一个数相对前一个数的倍率。
具体地,对最后一个数字每个质因子j,设有i个质因子j,将其分配到n个位置。采用隔板法将i个质因子j分成n份,两个隔板如无质因子则认为对应的盒子中数字与前一个数字相同。共n-1个隔板,i个数字,则对每个质因子j划分种类数为 C n − 1 + i n − 1 C_{n-1+i}^{n-1} Cn−1+in−1。对某一质因子j的分配方法,对另一质因子 j , j^, j,都可另生成 C n − 1 + i , n − 1 C_{n-1+i^,}^{n-1} Cn−1+i,n−1种全新的方法,其中 i , i^, i,是质因子 j , j^, j,的次数,以此类推。
const int MOD = 1e9 + 7;
class Comb {
vector<int> Facs, Invs;
void expand(size_t n) {
while(Facs.size() < n + 1) Facs.push_back(1ll * Facs.back() * Facs.size() % MOD);
if(Invs.size() < n + 1) { // 线性求阶乘的逆元
Invs.resize(n + 1, 0);
Invs.back() = 1;
for(int a = Facs[n], p = MOD - 2; p; p >>= 1, a = 1ll * a * a % MOD)
if(p & 1) Invs.back() = 1ll * Invs.back() * a % MOD; // 快速乘求 n! 的逆元
for(int j = n-1; !Invs[j]; --j) Invs[j] = 1ll * Invs[j+1] * (j + 1) % MOD;
}
}
public:
Comb() : Facs({1}), Invs({1}) {}
Comb(int n) : Facs({1}), Invs({1}) { expand(n); }
int operator() (int n, int k) {
if(k > n) return 0;
expand(n);
return (1ll * Facs[n] * Invs[n-k] % MOD) * Invs[k] % MOD;
}
};
Comb comb;
typedef long long ll;
class Solution {
public:
int idealArrays(int n, int maxValue) {
if(maxValue == 1) return 1;
bool isp[maxValue + 1];
isp[1] = false; isp[2] = true;
vector<int> p; p.push_back(2);
const int mod = 1e9+7;
for(int i = 3;i<=maxValue;i++)
{
isp[i]=true;
for(int j = 2;j * j <= i;j++) if(i%j==0) {isp[i]=false;break;}
if(isp[i]) p.push_back(i);
}
int ans = 1;
for(int last = 2;last <= maxValue;last++)
{
int temp = last;
if(isp[temp]) ans = (ans + n) % mod;
else //分解质因数
{
int cur = 0;
int t = 1;
while(temp > 1)
{
int count = 0; //当前质因数的个数
while(temp > 1 && temp % p[cur] == 0) {temp /= p[cur]; count++;}
if(count > 0) //如果当前质因数个数大于0
{
t = ((ll)1 * t * comb(n + count - 1,count)) % mod;
}
cur++;
}
ans = ( ans + t ) % mod;
}
}
return ans;
}
};
上述代码求组合数地模板非常好用,值得珍藏。
反思:没有想到可以将质因数分配到每个位置上,即枚举倍率来计数,只想到对每个长度,互相不同的数的种类数进行计数。并且一开始就只想使用开头固定的数字划分解空间了。