一道狄利克雷卷积模板题的组合做法

一道狄利克雷卷积模板题的组合做法


题目描述:

给定 f(x) [1,n] 上的取值,求函数:

g(x)=i1|xi2|i1iK|iK1f(iK)

[1,n] 上的取值。

狄利克雷卷积做法

容易发现, g=1k×f ,直接用狄利克雷卷积快速幂即可。

组合做法

显然, p 的所有因子在整除关系|下构成一个偏序,可以等价地转化为一张带自环而无其他环的图。则从 i1=p 开始的序列 i1,i2,,ik 与有向无环图上从 p 开始长度为k的路径一一对应。

考虑求从 p 开始长度为k的路径所有终点位置的和 h[p][k] ,容易写出方程:

h[p][k]=i|ph[i][k1]h[p][1]=dat[p]

然而这个dp的状态规模是 O(n2) 的,考虑原因:存在很长的链是因为可以多次在一个数上重复(即在自环上走)。设 H[p][k] 为长度为从 p 开始长度为k的所有不经过自环的终点位置的和,则:

H[p][k]=i|p,i<pH[i][k]

显然 klgK ,现在向路径上插入自环。考虑 H[n][p] h[n][K] 的贡献, pK 。需要在 p 个位置上插入Kp个自环,用隔板法分析可知为: (p+(Kp)1p1)=(K1p1) ,因此:

h[n][K]=pH[n][p](K1p1)

而答案 g(n) 等于 n 的所有因子的h[n][K]的和,即:

g(n)=i|nh[i][K]

自此问题得以解决。复杂度 O(nlg2n)

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;
struct node {
    int to, next;
} edge[MAXN*20];
int head[MAXN], top = 0; 
inline void push(int i, int j)
{ edge[++top] = (node){j, head[i]}, head[i] = top; }
int n, k;

void init()
{
    for (register int i = 1; i <= 100000; i++)
        for (register int j = 2; j*i <= 100000; j++)
            push(j*i, i);
}

long long f[MAXN][20], a[MAXN];
const long long mod = 1000000007ll;
long long fac[MAXN], inv[MAXN], g[MAXN], g2[MAXN];
long long power(int a, long long n)
{
    if (n == 0) return 1;
    long long b = a, ans = 1;
    for (int k = 0; k <= 30; k++) {
        if (n&(1ll<<k)) (ans *= b) %= mod;
        (b *= b) %= mod;
    }
    return ans;
}

inline long long choose(int K, int k)
{ return fac[K]*inv[k]%mod*inv[K-k]%mod; }

void init_c()
{   
    inv[0] = 1;
    for (int i = 1; i <= 100000; i++) inv[i] = (inv[i-1]*power(i, mod-2))%mod;
    fac[0] = 1;
    for (int i = 1; i <= 100000; i++) (fac[i] = fac[i-1]*i) %= mod;
}

void dp()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) scanf("%lld", &f[i][1]);
    for (int j = 2; j <= 17; j++)
        for (register int i = 1; i <= n; i++) {
            f[i][j] = 0;
            for (register int k = head[i]; k; k = edge[k].next) {
                int to = edge[k].to;
                (f[i][j] += f[to][j-1]) %= mod;
            }
        }
    for (register int i = 1; i <= n; i++) {
        long long ans = 0;
        for (register int j = 1; j <= 17; j++)
            (ans += f[i][j]*choose(k-1, j-1)%mod) %= mod;
        g[i] = ans;
    }// cerr << endl;
    memset(g2, 0, sizeof g2);
    for (register int i = 1; i <= n; i++) 
        for (register int j = 1; j*i <= n; j++)
            (g2[j*i] += g[i]) %= mod;
    for (int i = 1; i <= n; i++)
        printf("%lld ", g2[i]);
    puts("");
}

int main()
{
    freopen("b.in", "r" , stdin);
    freopen("b.out", "w", stdout);
    init();
    init_c();
    int T; scanf("%d", &T);
    while (T--) dp();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值