HDU 5628 Clarke and math——卷积,dp,组合

时间:2023-03-09 17:09:44
HDU 5628 Clarke and math——卷积,dp,组合

HDU 5628 Clarke and math

  本文属于一个总结了一堆做法的玩意......

题目

  简单的一个式子:给定$n,k,f(i)$,求

HDU 5628 Clarke and math——卷积,dp,组合

  然后数据范围不重要,重要的是如何优化这个做法。

  这个式子有$n$种问法,而且可以变式扩展,所以说这个式子也是比较重要的:

  我们约定如果给定了$n,k$那么我们的$g$写作$g_k(n)$,如果给定了$n,k$中间的任意一个,枚举另一个,或者另一个是变化的,那么另一个数记为$i,j$

  1. 把$1~n$或$1~k$的$g_k(i)$或$g_i(n)$都求出来(狄利克雷卷积或者dp)
  2. 快速求单个$g_k(n)$(乘法同构,某考试的题(被我用dp水了))
  3. 所有$f$都是1或者某个特定函数(Luogu月赛题
  4. 等等......

  下面算法单词回答部分有些由于模数过大而要动态求逆元的原因需要多个$log$

  为了区分我枚举的$k$和题目中的$k$,这里我们约定题目中的$k$写作大写的$K$

算法

狄利克雷卷积

  比较大众的算法,就是根据狄利克雷卷积($(f*g)(n)=\sum_{d|n}{f(d)*g(\frac{n}{d})}=\sum_{a*b=n}{f(a)*g{b}}$(这个$f,g$与题目无关))。

  我们首先令$k=1$,然后$g_1(i)=\sum_{i_1|i}{f(i_1)}=(f*h)(n)$,这个时候我们推一下知道$h(i)=1$了

  同时$k=2$时$g_2(i)=(f*h*h)(n)=(f*h^2)(n)$

  答案就是$(f*(h=1)^K)(i)$

  由于狄利克雷卷积满足结合律,所以可以用快速幂优化,每次计算狄利克雷卷积复杂度$O(nlog_2n)$,快速幂$O(log_2n)$

  代码(早期):

  

 #include <cstdio>
#include <cctype>
#include <cstring>
using namespace std; #define MAX_BUF 10000000
#define MAXN 100005
#define MODS 1000000007
#define LL long long struct FR{
char buf[MAX_BUF + ], *p1, *p2;
FR(){
p2 = (p1 = buf) + fread(buf, , MAX_BUF, stdin);
}
inline void flash(){
p2 = (p1 = buf) + fread(buf, , MAX_BUF, stdin);
}
inline char get_char(){
return p1 == p2 ? EOF : *p1++;
}
}fr; inline LL read(){
LL num = ;
char c, sf = ;
while(isspace(c = fr.get_char()));
if(c == '-') c = fr.get_char(), sf = -;
while(num = num * + c - , isdigit(c = fr.get_char()));
return num * sf;
} LL n, k, f[MAXN], g[MAXN], h[MAXN], tmp[MAXN]; inline void Dirchlet(LL *scr, LL *dst){
memset(tmp, , sizeof(tmp));
for(int i = , j; i * i <= n; i++)
for((tmp[i * i] += scr[i] * dst[i] % MODS) %= MODS, j = i + ; i * j <= n; j++)
(tmp[i * j] += scr[i] * dst[j] % MODS + scr[j] * dst[i] % MODS) %= MODS;
memcpy(dst, tmp, sizeof(tmp));
} int main(){
LL T = read();
while(T--){
n = read(), k = read();
for(LL i = ; i <= n; i++)
f[i] = read(), h[i] = , g[i] = ;
h[] = ;
while(k){
if(k & ) Dirchlet(g, h);
Dirchlet(g, g);
k >>= ;
}
Dirchlet(f, h);
for(LL i = ; i <= n; i++) printf("%lld%c", h[i], i == n ? '\n' : ' ');
}
return ;
}

dp+组合数学

  以前看的:复杂度$O(nlog_2^2n)$(<--原文),以前拿这个水过题,例如快速求单个$g_k(n)$的时候效果比较好。

  思路大概就是dp出因子的贡献然后计算出组合,即某个因子在所有可能传递情况下是如何传递的。

  解:设$dp[i][j]$表示在$n=i$的时候,中间的$\Sigma$部分选择了$j$个因子(原文是不同的因子,但是我并不理解)时对于第$i$个位置所产生的贡献。

  所以$dp[i][k] += dp[j][k - 1](j|i,k \leq log(n),dp[i][0] = f[i])$,举个栗子:在最后一个$\Sigma$(第k层)的位置$i_{k}|i_{k-1}$,而第k-1层枚举因子的时候就会有一个$x * i_{k} = i_{k - 1}$,这个贡献最终是计入到了$k-1$上去的。但是从$f(j)$到$g(i)$中间的$\Sigma$在不同的位置消掉了$r$次因子不止一种方案,$f(j)$不止做了一次贡献,这个方案数就是$C(K,r)$。

  通俗地说:我们求$g(i)$,但是这个$g(i)$是由多个$f(j)$凑出来的,如何将$g(i)$和$f(j)$扯上关系?就是中间$k$个$\Sigma$有些$\Sigma$会吃掉一些因子,假设$K$个$\Sigma$吃了$r$个因子,那么也就是说有些$\Sigma$吃了我的因子,而有些没有,这些情况都会导致$f(j)$对$g(i)$做贡献。因此如果现在我知道我的因子一共被$K$个$\Sigma$吃了$r$个的话那么就相当于$K$个物品中选出$r$个物品,方案数$C(K,r)$。

  也就是$g_K(n)=C(K,j)*dp[n][j](j \leq log(n))$

  但是时间复杂度都会有瓶颈(dp是$O(nlog_2^2n)$,单词询问$O(log_2^2n)$),不过适用性很好。

  代码(早期):

 #include <cstdio>
#include <cctype>
#include <cstring>
using namespace std; #define MAX_BUF 10000000
#define MAXK 25
#define MAXN 100005
#define MODS 1000000007
#define LL long long struct FR{
char buf[MAX_BUF + ], *p1, *p2;
FR(){
p2 = (p1 = buf) + fread(buf, , MAX_BUF, stdin);
}
inline void flash(){
p2 = (p1 = buf) + fread(buf, , MAX_BUF, stdin);
}
inline char get_char(){
return p1 == p2 ? EOF : *p1++;
}
}fr; inline LL read(){
LL num = ;
char c, sf = ;
while(isspace(c = fr.get_char()));
if(c == '-') c = fr.get_char(), sf = -;
while(num = num * + c - , isdigit(c = fr.get_char()));
return num * sf;
} LL n, k, f[MAXN], c[MAXN][MAXK], dp[MAXN][MAXK], fac[MAXN]; inline LL Get_Pow(LL base, LL t){
LL ret = ;
while(t){
if(t & ) (ret *= base) %= MODS;
(base *= base) %= MODS;
t >>= ;
}
return ret;
} inline LL Get_C(LL a, LL b){
if(c[a][b]) return c[a][b];
LL u = fac[a], d = fac[a - b] * fac[b] % MODS;
return u * Get_Pow(d, MODS - ) % MODS;
} const LL Up_K = ; int main(){
LL T = read();
fac[] = ;
for(int i = ; i < MAXN; i++) fac[i] = fac[i - ] * i % MODS;
while(T--){
memset(dp, , sizeof(dp));
n = read(), k = read();
for(LL i = , j; i <= n; i++)
for(dp[i][] = f[i] = read(), j = i << ; j <= n; j += i)
for(LL r = ; r < Up_K; r++)
(dp[j][r + ] += dp[i][r]) %= MODS;
for(LL i = ; i <= n; i++){
LL ans = ;
for(LL r = ; r < Up_K; r++)
(ans += Get_C(k, r) * dp[i][r] % MODS) %= MODS;
printf("%lld%c", ans, i == n ? '\n' : ' ');
}
}
return ;
}

乘法同构

  这个我看不懂它的定义,但是乘法同构的思路好像和下面差不多,但是时间复杂度差了一些,但是这里贴出原来的题和题解:对于上面式子在$n,k<=1e5$下询问$1e3$次(可以用dp+组合数学做)。

定义乘法同构:
$A=p[1]^{a[1]} * p[2]^{a[2]} * ... * p[n]^{a[n]}$
$B=q[1]^{b[1]} * q[2]^{b[2]} * ... * q[n]^{b[n]}$
其中p[i]与q[i]皆为质数
将数组a与b降序排序后如果是完全相同的,那么称A与B是乘法同构的
如 2*2*2*2*3*3*5 与 7*11*11*3*3*3*3 同构 我们发现,10^5内在乘法同构下本质不同的数字只有165个
定义kind[x]:x在乘法同构下所属于的种类
对着165个本质不同的数构造出矩阵A
我们发现,$f_0[d]$对$f_k[x]$(d是x的因子)的贡献为$f_0[d] * A^k[kind[1]][kind[x/d]]$
所以我们只需要A^k[kind[1]][]这一行就够了
预处理$A,A^2,A^4,A^8,...O(165^3*log(n))$
对于每次询问因为最终只需要一行,可以优化到$O(165^2*log(n))$
总复杂度$O(165^3*log(n) + m*165^2*log(n))$

组合数学

  这个最劲$O(nlog_2n)$,然后也可以解决大部分问题,预处理$O(nlog_2n)$以及单次回答$O(log_2n)$,部分过程受HJWJBSR启发.

  而且目前在HDU上只有46ms(register优化竟然失效了,速度还慢一些)。

HDU 5628 Clarke and math——卷积,dp,组合

  思路:

  我们按照上一个dp+组合数学的思路,我们设$h(j)$表示$f(i)$对$g(i*j)$的贡献,如果算出了$h(1)~h(n)$的取值,那么就可以枚举因数在$O(nlog_2n)$的时间内算出$f(1)~f(n)$。

  如何计算$h(i)$:我们首先发现$h(i)$只和$K$和$i*j$的因子有关,同时发现假设我们我们需要求$g(k_1^a)$和$g(k_2^a)$,在$K$一样的情况下,我们传递因子的方案是相同的,也就是以前的$h(k^a)=C(a+K-1,a)$(上面一句话的$k,k_1,k_2$都是质因数)!然后由于每个因子传递的时候相互独立!所以根据乘法原理,假设对要求的$h(n)$的$n$分解为$n = k_1^{a_1}*k_2^{a_2}...k_x^{a_x}$,然后$h(n)=h(k_1^{a_1}*k_2^{a_2}...k_x^{a_x})=h(k_1^{a_1})*...*h(k_x^{a_x})$。我们发现这是一个积性函数!可以用线性筛求出来,由于中间需要求某个数是某个质数的多少次幂,所以带log。

  代码如下:

 #include <cstdio>
#include <cctype>
#include <cstring> //User's Lib using namespace std; char buf[], *pc = buf;
char outp[], *op = outp; extern inline void Main_Init(){
static bool INITED = false;
if(INITED){
fwrite(outp, , op - outp, stdout);
fclose(stdin), fclose(stdout);
} else {
fread(buf, , , stdin);
INITED = true;
}
} static inline int read(){
int num = ;
char c;
while((c = *pc ++) < );
while(num = num * + c - , (c = *pc ++) >= );
return num;
} inline void Write(const int &tar){//You Need To Write '-' and '\n' By Hand
if(!tar) return ;
Write(tar / );
*op ++ = (tar % ) ^ ;
} namespace LKF{
template <typename T>
extern inline T abs(T tar){
return tar < ? -tar : tar;
}
template <typename T>
extern inline void swap(T &a, T &b){
T t = a;
a = b;
b = t;
}
template <typename T>
extern inline void upmax(T &x, const T &y){
if(x < y) x = y;
}
template <typename T>
extern inline void upmin(T &x, const T &y){
if(x > y) x = y;
}
template <typename T>
extern inline T max(T a, T b){
return a > b ? a : b;
}
template <typename T>
extern inline T min(T a, T b){
return a < b ? a : b;
}
} //Source Code const int MAXN = ;
const int MAXK = ;
const int MODS = ; int n, k;
int fac[MAXN];
int f[MAXN], h[MAXN], g[MAXN]; inline int Get_Pow(int base, int times){
int ret = ;
while(times){
if(times & ) ret = 1ll * ret * base % MODS;
base = 1ll * base * base % MODS;
times >>= ;
}
return ret;
} inline int Get_Inv(const int &tar){
return Get_Pow(tar, MODS - );
} int tot;
int prime[MAXN];
bool is_prime[MAXN]; inline void Get_Prime(){
for(int i = ; i < MAXN; i++){
if(!is_prime[i]) prime[++ tot] = i;
for(int j = ; j <= tot; j++){
int tmp = i * prime[j];
if(tmp >= MAXN) break;
is_prime[tmp] = true;
if(i % prime[j] == ) break;
}
}
} inline void Get_Fac(){
fac[] = ;
for(int i = ; i < MAXN; i++)
fac[i] = 1ll * fac[i - ] * i % MODS;
} int c[MAXK]; inline int Get_C(const int &a, const int &b){//a > b
int tmp = 1ll * fac[a - b] * fac[b] % MODS;
return 1ll * fac[a] * Get_Inv(tmp) % MODS;
} inline void Get_C(const int &k){
for(int i = ; i < MAXK; i++)
c[i] = Get_C(i + k, i);
} inline void Get_H(){
int cnt = ;
h[] = ;
for(int i = ; i <= n; i++){
if(!is_prime[i]) h[i] = c[], cnt ++;
for(int j = ; j <= cnt; j++){
int tmp = i * prime[j];
if(tmp > n) break;
if(i % prime[j]) h[tmp] = 1ll * h[i] * h[prime[j]] % MODS;
else {
int Cnt = ;
while(!(tmp % prime[j])) tmp /= prime[j], Cnt ++;//计算幂次
h[i * prime[j]] = 1ll * h[tmp] * c[Cnt] % MODS;
break;
}
}
}
} int main(){
Main_Init();
Get_Fac();
Get_Prime();
int T = read();
while(T --){
memset(g, , sizeof(int) * (n + ));
n = read(), k = read() - ;
for(int i = ; i <= n; i++)//O(n)
f[i] = read();
Get_C(k);//O(log^2)
Get_H();//O(nlogn)
for(int i = ; i <= n; i++)//O(n + n / 2 + ...) = O(nlogn)(?)
for(int j = ; i * j <= n; j++)
(g[i * j] += 1ll * f[i] * h[j] % MODS) %= MODS;
for(int i = ; i <= n; i++){
if(g[i]) Write(g[i]);
else *op ++ = '';
*op ++ = (i == n ? '\n' : ' ');
}
}
Main_Init();
return ;
}