【JZOJ 3492】【NOIP2013模拟联考12】数数(count)

Description

ztxz16从小立志成为码农,因此一直对数的二进制表示很感兴趣。今天的数学课上,ztxz16学习了等差数列的相关知识。我们知道,一个等差数列可以用三个数A,B,N表示成如下形式:

B + A, B + 2 * A, B + 3 * A, …, B + N * A

ztxz16想知道对于一个给定的等差数列,把其中每一项用二进制表示后,一共有多少位是1,但他的智商太低无法算出此题,因此寻求你的帮助。

Solution

看到这种数论题就没有淦的欲望(结果这并不是数论QwQ)

发现A较小,考虑把B%A,

剩下直接上数位DP(并不知道为什么m那么小),

设f[i][j]表示做到m的二进制i为,余数为j,这里的余数只用记录二进制大于等于i的部分,也就是只有大于等于 2i 对后面的有影响,
再枚举当前位选什么即可

复杂度: O(Alog(m))

Code

#include 
#include 
#define fo(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
typedef long long LL;
const int N=25000,M=55;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int n;
LL B,m,ans;
LL er[M+5];
LL f[M+5][N],g[M+5][N];
LL f1[M+5][N],g1[M+5][N];
LL Doit(int n,LL B,LL m)
{
    memset(f,0,sizeof(f));
    memset(g,0,sizeof(g));
    memset(f1,0,sizeof(f1));
    memset(g1,0,sizeof(g1));
    f1[1][B]=g1[1][B]=1;
    fo(i,1,M)fo(j,0,n-1)if(f1[i][j])
    {
        f[i+1][(j+n)>>1]+=f[i][j]+(1&(j+n))*f1[i][j];
        f1[i+1][(j+n)>>1]+=f1[i][j];
        f[i+1][j>>1]+=f[i][j]+(1&j)*f1[i][j];
        f1[i+1][j>>1]+=f1[i][j];
        if(m&er[i])
        {
            g[i+1][j>>1]+=f[i][j]+(1&j)*f1[i][j];
            g1[i+1][j>>1]+=f1[i][j];
            if(g1[i][j])
            {
                g[i+1][(j+n)>>1]+=g[i][j]+(1&(j+n))*g1[i][j];
                g1[i+1][(j+n)>>1]+=g1[i][j];
            }
        }else if(g1[i][j])
        {
            g[i+1][j>>1]+=g[i][j]+(1&j)*g1[i][j];
            g1[i+1][j>>1]+=g1[i][j];
        }
    }
    LL ans=0;
    fo(i,0,n-1)ans+=g[M][i];
    return ans;
}
int main()
{
    int q,w,_;
    er[1]=1;fo(i,2,M+3)er[i]=er[i-1]<<1;
    for(read(_);_;--_)
    {
        scanf("%d%lld%lld",&n,&B,&m);
        m--;B+=(LL)n;
        ans=Doit(n,B%n,m+B/n);
        printf("%lld\n",ans-Doit(n,B%n,B/n-1));
    }
    return 0;
}

你可能感兴趣的:(数位DP)