AtCoder Beginner Contest 132 F Small Products

题意:给出K和N。问长度为K的且任相邻两位乘积不超过N的序列个数。

解法:这道题很有意思,解法是参考https://blog.csdn.net/mxYlulu/article/details/94664000https://www.cnblogs.com/Dup4/p/11148059.html这两位大佬的。

两位大佬的代码不同,解法是大同小异的。这里说下我的理解。

首先很容易想到k*n的做法:dp[i][j]代表长度为i的序列第i个数为j的方案数,dp[i][j]=sigma(dp[i-1][k]) (k*j<=n) 其实就是 dp[i][j]=sigma(dp[i-1][k]) (1<=k<=n/j) 那么这么可以通过前缀和优化达到n*k的时间复杂度。但是还是超时。n的大小达到10^9级别,那么我们就得想办法从这个n优化dp状态。

我们观察上面的dp方程,发现其实对于许多j,因为n/j的值相等,所以k的范围相等,所以dp方程相等,其实他们的答案是一样的!!!这是一个重大发现,而且这些相等的j其实就是n的整除分块(当然必须得先了解这个知识才能做这道题)!于是显然得优化就是想办法通过整除分块把这些答案相同的dp[i][j]合并起来成为一块然后直接通过乘法得到这块的答案免去一个个计算。想懂了这里这道题就可以获得AC了。

代码是学习上面的Dup4大佬的,大佬写的非常简洁易懂。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
const int P=1e9+7;
int n,m,k,a[N],l[N],dp[110][N];
map<int,int> b; 

int main()
{
    cin>>n>>k;
    for (int x=1,gx;x<=n;x=gx+1) {
        gx=n/x ? min(n/(n/x),n) : n;
        a[++m]=gx; l[m]=gx-x+1;
        b[gx]=m;
    }
    for (int j=0;j<=m;j++) dp[0][j]=1;
    for (int i=1;i<=k;i++) {
        for (int j=1;j<=m;j++) {
            dp[i][j]=(dp[i][j-1]+(LL)l[j]*dp[i-1][b[n/a[j]]]%P)%P;
        }
    }
    cout<<dp[k][m]<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/clno1/p/11398518.html