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

本文详细解析了一种复杂度为O(m)的算法解题思路,通过整除分块和差分技巧优化时间复杂度,针对特定数据范围n≤100,m≤109的题目提供高效解决方案。

题解 不知道该叫啥

题目描述

屏幕截图_10.png

【数据范围】

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

具体做法与心路历程

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

具体做法

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

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

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

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

fif_ifi表示第iii个块的个数,ri=Mir_i=\frac{M}{i}ri=iM,第iii 个块的数的范围是aia_iai~bib_ibi,leni=bi−ai+1len_i=b_i-a_i+1leni=biai+1

那么如果bi≤rkb_i \leq r_kbirk,fi+=fk×lenif_i += f_k \times len_ifi+=fk×leni

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

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

Code\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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值