DP进阶

1.数位DP

https://www.luogu.com.cn/problem/P4317

\(ans_i\)表示\(i\)位为\(1\)的范围内二进制数。

答案就是\(\sum_{i=1}^{log \ n}i^{ans_i}\)快速幂求一下。

然后呢这个\(log \ n=100\)

所以估计是\(O(log^3 \ n)\)

对于每一个\(ans_i\)求法。

考虑数位DP。

\(DP[i][j]\)表示\(ans_i\)\(2^j\)内的答案。

\(DP[i][j]=DP[i-1][j-1]+DP[i][j-1]\)

发现\(DP[i][j]=\binom{j}{i}\)

不过这个上界不好处理。

\(N\)拆成\(2\)进制下\(\sum_{i=1}^{logN}2^i*pig_i(pig_i=0,1)\)

分段来算答案。

要考虑必然\(1\)

凑齐\(3\)\(logN\)

快速幂没判0调了一小时

#include <bits/stdc++.h>

using namespace std;

const long long mod=1e7+7;
long long n;
long long c[105][105];
long long ans[105];
long long an=1;

long long qpow(long long x,long long k)
{
    if(k==0)return 1;
    if(k==1)return x;
    long long qq=qpow(x,k/2);
    if(k&1)
    {
        return ((qq*qq)%mod*x)%mod;
    }
    else
    {
        return (qq*qq)%mod;
    }
}

int main()
{
    int i,j,k;
    cin>>n;
    for(i=0;i<=50;i++)
    {
        c[i][i]=c[i][0]=1;
    }
    for(i=2;i<=50;i++)
    {
        for(j=1;j<i;j++)
        {
            c[i][j]=c[i-1][j]+c[i-1][j-1];
        }
    }
    k=0;
    for(i=50;i>=0;i--)
    {
        if((n>>i)&1)
        {
            for(j=1;j<=i;j++)
            {
                ans[k+j]+=c[i][j];
            }
            ans[++k]++;
        }
    }
    for(i=1;i<=50;i++)
    {
        an*=qpow(i,ans[i]);
        an%=mod;
    }
    cout<<an;
	return 0;
}

https://www.luogu.com.cn/problem/P6218

这里\(logN=30\)

大可以\(O(N^4)\)

哦跟上题一样,那没事了。

换一种思路。

\(N<=2*10^9\)下跑暴力需要花\(4\)分钟左右。

也就是说记录下前缀和是不行的,吗?

全部记录是不行的但是只要\(\text{枚举数数}<=10^7\)就是可以的。

那么就可以每隔\(w\)(块长)记录一个前缀和。

达标发程序里。

打表的长度最好要小于\(10^6\)

复杂度是\(O(w)\)

空间复杂度是\(O(\frac{N}{w})\)

这题采用\(w=400000\)因为题解是这样

分块打表的极限。

\(w=10^7\)

\(N=10^{14}\)

大概。

#include<bits/stdc++.h>

using namespace std;

const int w=4e5;
const int num[5005]={表};

int l,r;
int ll,rr;
int ans,c[2];

int main()
{
    int i,j,k;
    cin>>l>>r;
    ll=(l-1)/w+1;
    rr=(r-1)/w+1;
    ans=num[rr]-num[ll-1];
    for(i=(ll-1)*w+1;i<l;i++)
    {
        j=i;
        c[0]=c[1]=0;
        while(j)
        {
            c[j%2]++;
            j/=2;
        }
        if((c[0]>=c[1]))
        {
            ans--;
        }
    }
    for(i=r+1;i<=rr*w;i++)
    {
        j=i;
        c[0]=c[1]=0;
        while(j)
        {
            c[j%2]++;
            j/=2;
        }
        if((c[0]>=c[1]))
        {
            ans--;
        }
    }
    cout<<ans;
    return 0;
}

https://www.luogu.com.cn/problem/P4127

卡分块打表。

\(DP[i]\)表示数字和为\(i\)数被$i整除的数个数。

无法转移。

\(DP[i][j]\)表示数字和为\(i\)数被\(i\)除余\(j\)数个数。

诶好啊,可以转移了。

然后改成\(DP[i][j][k]\)表示前\(i\)位数字和为\(j\)数被\(j\)除余\(k\)数个数。

\(O(lg^3N)\)常数稍大。

posted @ 2021-01-26 13:48  xiaopangfeiyu  阅读(54)  评论(0)    收藏  举报