3492. 【NOIP2013模拟联考12】数数(count)(循环节/DP)

来源:互联网 发布:天龙八部装备精通数据 编辑:程序博客网 时间:2024/06/07 13:35

Problem

给定一等差数列B+A,B+2A,B+3A,...,B+NA

求其中每一项转化成二进制后的1的个数和.

Data constraint

对于30%的数据:

1<=T<=20 , 1<=A<=10000 , 1<=B<=10^16 , 1<=N<=10^3

对于60%的数据:

1<=T<=20 , 1<=A<=10000 , 1<=B<=10^16 , 1<=N<=10^9

对于100%的数据:

1<=T<=20 , 1<=A<=10000 , 1<=B<=10^16 , 1<=N<=10^12

Perface

  • 此题方法很多,先介绍一种我想到的方法,也是题解的方法,然后是数位Dp的方法,最后看看能否把类欧也补上.

Solution

Method-1

  • 我们依次统计每一位1出现次数.

  • lenA+B的二进制位个数.

  • 我们发现len+1位以前的其实统计很方便.

  • 因为A+BA+(2k+1)B的第k位是相同的.

  • 这样可以组成一个长度为2k的循环节,每个循环节里1的个数一定是循环节长度的一半(2k1).

  • 但是直接算完后可能会有余.

  • 怎么办?

  • 我们可以把一循环节里的一段1和一段0直接算出,这样子效率会快很多.

  • 我们可以采用逼近的思想.

  • 直观的想法就是先跳1,然后跳2,然后跳4,倍增的思想.

  • 注意一下细节可发现,如果A=1010100

  • 那么第三位时才会有循环节,所以我们可以先算出后两位直接加了多少,然后再算其他位.

  • 算其他位时我们还可以把LenA位的算好.

  • 不然逼近的时候很麻烦.

  • 代码如下:

#include <cstdio>#include <cstring>#include <iostream>#define ll long long#define N 1000000#define M 20#define T 20000using namespace std;ll A,B,n,len,t,ans,ste,st,res,sum,lena,lenb,now,cnt,up,Tn,s;ll a[N],g[N],d[N],las,k;ll f[T][M];void GET(ll t){    len = 0;    while (t) d[++len]=t&1, t>>=1;}ll leng(ll t){    ll len = 0;    while (t) t>>=1, len++;    return len;}int main(){    freopen("data.in","r",stdin);    //freopen("data2.out","w",stdout);    for (scanf("%lld",&Tn);Tn;Tn--)    {        scanf("%lld%lld%lld",&A,&B,&n), ans=cnt=las=k=0;        GET(A);        while (d[las+1]==0 && las+1<=len) las++;        GET(A), memcpy(a,d,sizeof(d)), lena = leng(A);        GET(B);        while (++k<=las) ans+=d[k]*n;        ll Maxlen = leng(B+A*n), Maxer = 1 << (lena-las), C = B, MO = 1 << lena;        while (cnt++<Maxer)        {            C = (C + A) % MO, t = C;            memset(f[cnt],0,sizeof(f[cnt]));            while (t) f[cnt][++f[cnt][0]]=t&1, t >>= 1;        }        st = 1;        for (ll i = las+1; i <= lena; i++)        {            st = st << 1, ans += (n / st) * st / 2, res = n % st;            for (ll j = 1; j <= res; j++)                ans +=f[j][i];        }        int LEN = leng(A+B);        GET(A+B); memcpy(a,d,sizeof(d));        cnt = lena-1;        for (ll i = lena+1; i <= Maxlen; i++)        {            st = st << 1, cnt++, ans += (n / st) * st / 2;            t = A + B, sum = 0, res = n % st;            if (i<=LEN) now = a[i]; else now=0;            while (sum < res)            {                up = 1, s = ste = 0, GET(t);                for (ll j = min(cnt,len); j >= 1; j--)                    s = (s << 1) + d[j];                while (leng(s)<=cnt)                {                    if (leng(s+up*A)>cnt) up = 1;                    t += up * A, s += up * A, ste += up, up <<= 1;                }                ans += now * min(ste,(res-sum)), now = 1 - now, sum += ste;            }        }        printf("%lld\n",ans);    }}

Method-2

  • 上述算法只有30分,与暴力相当.

  • 仔细考虑后可以发现,上述代码实际慢的在于逼近那一块

  • 为什么不可以直接算出要逼近多少,要一个一个跳呢?

  • 于是,我改成了直接算出要加多少个A.

#include <cstdio>#include <cstring>#include <iostream>#define ll long long#define N 1000000#define M 20#define Mn 60#define T 20000using namespace std;ll A,B,n,len,t,ans,ste,st,res,sum,lena,lenb,now,cnt,up,Tn,s;ll a[N],g[N],d[N],shl[Mn+1],las,k;ll f[T][M];void GET(ll t){    len = 0;    while (t) d[++len]=t&1, t>>=1;}ll leng(ll t){    ll len = 0;    while (t) t>>=1, len++;    return len;}int main(){    for (int i=1;i<=Mn;i++)        shl[i]=1LL << i;    for (scanf("%lld",&Tn);Tn;Tn--)    {        scanf("%lld%lld%lld",&A,&B,&n), ans=cnt=las=k=0;        GET(A); while (d[las+1]==0 && las+1<=len) las++;        GET(A), memcpy(a,d,sizeof(d)), lena = leng(A);        GET(B); while (++k<=las) ans+=d[k]*n;        ll Maxlen = leng(B+A*n), Maxer = shl[lena-las], C = B, MO = 1 << lena;        while (cnt++<Maxer)        {            C = (C + A) % MO, t = C;            for (int i = 1; i <= f[cnt][0]; i++)                f[cnt][i]=0; f[cnt][0]=0;            while (t) f[cnt][++f[cnt][0]]=t&1, t >>= 1;        }        st = 1;        for (ll i = las+1; i <= lena; i++)        {            st = st << 1, ans += (n / st) * st / 2, res = n % st;            for (ll j = 1; j <= res; j++)                ans +=f[j][i];        }        int LEN = leng(A+B);        GET(A+B); memcpy(a,d,sizeof(d));        cnt = lena-1;        for (ll i = lena+1; i <= Maxlen; i++)        {            st = st << 1, cnt++, ans += (n / st) * st / 2, t = A + B, sum = 0, res = n % st;            if (i<=LEN) now = a[i]; else now=0;            while (sum < res)                up = 1, s = 0, len=leng(t), s = t % shl[min(cnt,len)], ste = (shl[cnt] - s - 1) / A + 1,                t += ste*A, ans += now * min(ste,(res-sum)), now = 1 - now, sum += ste;        }        printf("%lld\n",ans);    }}

Method-3

  • 这道题可以数位DP!!!

  • 我们发现这道题关键在于A很小,想想怎么利用

  • 噢!!如果把等差数列里的每一个数模上A,那么它们是同一个数,而这样子的数只要在B+AB+AN范围内都是合法的.

  • 于是数位DP走起.

  • 一般的数位dp是把答案拆为ans(b)-ans(a-1)的,但为了跟一波形式,也打了个直接一遍搞的.

  • 我们就设f[i][j][0/1][0/1]表示到第i位构造的数模上A为j,0表示等于下限,1表示大于下限,0表示等于上限,1表示小于上限.

  • 然后我们就可以分四种情况愉快地DP了.

  • 细节比较多.

  • 最好是把状态写在纸上,然后想清楚了一次打过,这种题一定要一气呵成,想好了就一定要打对,而且要快.

#include <iostream>#include <cstdio>#include <cstring>#define fo(i,a,b) for (i=a;i<=b;i++)#define fd(i,a,b) for (i=a;i>=b;i--)#define NN 55#define MM 10010#define ll long longusing namespace std;ll T,A,B,N,i,j,len,lem,t,x,y,t1,t2;ll f[NN][MM][2][2],g[NN][MM][2][2],d[NN],b[NN];int main(){    for(scanf("%lld",&T);T;T--)    {        scanf("%lld%lld%lld",&A,&B,&N);        memset(f,0,sizeof(f)),memset(g,0,sizeof(g)),memset(b,0,sizeof(b));        t = B+A*N, len = 0; while (t) d[++len]=t&1, t>>=1;        t = B+A,   lem = 0; while (t) b[++lem]=t&1, t>>=1;        if (b[len]==1) f[1][1%A][0][0]=g[1][1%A][0][0]=1; else f[1][1%A][1][0]=g[1][1%A][1][0]=1, f[1][0][0][1]=1;        fo(i,1,len-1)            fo(j,0,A-1)            {                x = (j*2+1)%A, y = (j*2)%A;                t1=f[i][j][0][0], t2=g[i][j][0][0];                if (b[len-i]==1)                    f[i+1][x][0][0]+=t1, g[i+1][x][0][0]+=t1+t2;                else                {                    if (d[len-i]==1)                        f[i+1][x][1][0]+=t1, g[i+1][x][1][0]+=t1+t2, f[i+1][y][0][1]+=t1, g[i+1][y][0][1]+=t2;                    else                        f[i+1][y][0][0]+=t1, g[i+1][y][0][0]+=t2;                }                t1=f[i][j][0][1], t2=g[i][j][0][1];                if (b[len-i]==1)                    f[i+1][x][0][1]+=t1, g[i+1][x][0][1]+=t1+t2;                else                {                    f[i+1][x][1][1]+=t1, g[i+1][x][1][1]+=t1+t2;                    f[i+1][y][0][1]+=t1, g[i+1][y][0][1]+=t2;                }                t1=f[i][j][1][0], t2=g[i][j][1][0];                if (d[len-i]==1)                    f[i+1][x][1][0]+=t1, g[i+1][x][1][0]+=t1+t2,                    f[i+1][y][1][1]+=t1, g[i+1][y][1][1]+=t2;                else                    f[i+1][y][1][0]+=t1, g[i+1][y][1][0]+=t2;                t1=f[i][j][1][1], t2=g[i][j][1][1];                f[i+1][x][1][1]+=t1, g[i+1][x][1][1]+=t1+t2;                f[i+1][y][1][1]+=t1, g[i+1][y][1][1]+=t2;            }        B = B % A;         printf("%lld\n",g[len][B][0][0]+g[len][B][0][1]+g[len][B][1][0]+g[len][B][1][1]);    }}
  • 但是不难发现这样实在是太丑了,我学习了一下彭大爷的打法,于是有了下面这个常数小一点的代码:
#include <iostream>#include <cstdio>#include <cstring>#define fo(i,a,b) for (i=a;i<=b;i++)#define fd(i,a,b) for (i=a;i>=b;i--)#define NN 55#define MM 10010#define ll long longusing namespace std;ll T,A,B,N,i,j,p,q,p1,p2,x,y,len,lem,t;ll f[NN][MM][2][2],g[NN][MM][2][2],d[NN],b[NN];int main(){    for(scanf("%lld",&T);T;T--)    {        scanf("%lld%lld%lld",&A,&B,&N);        memset(f,0,sizeof(f)),memset(g,0,sizeof(g)),memset(b,0,sizeof(b));        t = B+A*N, len = 0; while (t) d[++len]=t&1, t>>=1;        t = B+A,   lem = 0; while (t) b[++lem]=t&1, t>>=1;        f[0][0][0][0]=1;        fo(i,0,len-1)            fo(j,0,A-1)                fo(p,0,1)                    fo(q,0,1)                        if (f[i][j][p][q])                            fo(x,0,1)                                if ((p|(x>=b[len-i])) && (q|(x<=d[len-i])))                                {                                    y=(j*2+x)%A, p1=p|(x>b[len-i]), p2=q|(x<d[len-i]);                                    f[i+1][y][p1][p2]+=f[i][j][p][q];                                    g[i+1][y][p1][p2]+=g[i][j][p][q]+f[i][j][p][q]*x;                                }        B = B % A;        printf("%lld\n",g[len][B][0][0]+g[len][B][0][1]+g[len][B][1][0]+g[len][B][1][1]);    }}
阅读全文
0 0
原创粉丝点击