题解 不知道该叫啥 数论分块优化dp

题解 不知道该叫啥

题目描述

屏幕截图_10.png

【数据范围】

n ≤ 100 , m ≤ 1 0 9 n \leq 100 , m \leq 10^9 n100,m109

具体做法与心路历程

考场上首先想到的是 O ( n m ) O(nm) O(nm)的做法。后来推不动了就去看后面的题,后面题也推不动了上了个厕所回来就想出来了。真的要充分利用。还是太菜了,别人10min的切的题,我用了1h

具体做法

按照考试时的时间顺序的想法。

首先观察性质:设已经有了个长度为 n n n的序列,那么求 n + 1 n+1 n+1的序列只与第 n n n个数有关。且设第 n n n个数为 i i i,那么接下来可以填 1 1 1~ M i \frac{M}{i} iM内的所有数。

那么可以设 f n , i f_{n,i} fn,i表示长度为 n n n的序列第 n n n个位置为 i i i的方案数。那么有
f n , i = ∑ k M i f n − 1 , k f_{n,i}=\sum_{k}^{\frac{M}{i}}{f_{n-1,k}} fn,i=kiMfn1,k
显然可以 O ( m ) O(m) O(m)转移,但时间复杂度承受不住。

考虑整除分块,对于 M i \frac{M}{i} iM相同的数可以放在同一个块了,因为他们的长度每 + 1 +1 +1的答案是相同的。

f i f_i fi表示第 i i i个块的个数, r i = M i r_i=\frac{M}{i} ri=iM,第 i i i 个块的数的范围是 a i a_i ai~ b i b_i bi, l e n i = b i − a i + 1 len_i=b_i-a_i+1 leni=biai+1

那么如果 b i ≤ r k b_i \leq r_k birk, f i + = f k × l e n i f_i += f_k \times len_i fi+=fk×leni

根据整除分块的性质, f f f最多有 M \sqrt{M} M 个。转移最坏为 O ( m ) O(\sqrt{m}) O(m ),还是承受不了。

注意到为区间加,那么可以差分一下(二分差分位置),对于每次 O ( m ) O(\sqrt{m}) O(m )统计答案。(其实差分位置是有规律的,不用二分)。

C o d e \mathcal{Code} Code

/*******************************
Author:galaxy yr
LANG:C++
Created Time:2019年10月24日 星期四 15时43分32秒
*******************************/
#include<cstdio>
#include<algorithm>
#define int long long

using namespace std;

struct IO{
    template<typename T>
    IO & operator>>(T&res)
    {
        T q=1;char ch;
        while((ch=getchar())<'0' or ch>'9')if(ch=='-')q=-q;
        res=(ch^48);
        while((ch=getchar())>='0' and ch<='9') res=(res<<1)+(res<<3)+(ch^48);
        res*=q;
        return *this;
    }
}cin;

const int maxn=1e5+10;
const int mod=1e9+7;

int n,m,a[maxn],b[maxn],cnt,len[maxn],f[maxn],r[maxn],tmp[maxn],s[maxn];

void init()
{
    for(int l=1,r;l<=m;l=r+1)
    {
        cnt++;
        r=m/(m/l);
        a[cnt]=l,b[cnt]=r;len[cnt]=r-l+1;
        f[cnt]=len[cnt];
        ::r[cnt]=m/l;
    }
}

int erfen(int val)
{
    int l=0,r=cnt,mid,ans=0;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(val>=b[mid])
            ans=mid,l=mid+1;
        else
            r=mid-1;
    }
    return ans;
}

void dp()
{
    for(int i=1;i<=cnt;i++) tmp[i]=0;
    for(int k=1;k<=cnt;k++)
    {
        tmp[1]=(tmp[1]+f[k])%mod;
        int y=erfen(r[k])+1;
        tmp[y]=(tmp[y]-f[k]+mod)%mod;
    }
    for(int i=1;i<=cnt;i++)
        tmp[i]=(tmp[i]+tmp[i-1])%mod,f[i]=1ll*tmp[i]*len[i]%mod;
}

signed main()
{
    //freopen("noname.in","r",stdin);
    //freopen("noname.out","w",stdout);
    cin>>n>>m;
    init();
    while(--n)
        dp();
    long long ans=0;
    for(int i=1;i<=cnt;i++)
        ans=(ans+f[i])%mod;
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值