3492. 【NOIP2013模拟联考12】数数(count)(循环节/DP)
来源:互联网 发布:天龙八部装备精通数据 编辑:程序博客网 时间:2024/06/07 13:35
Problem
给定一等差数列
求其中每一项转化成二进制后的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出现次数.
令
len 为A+B 的二进制位个数.我们发现
len+1 位以前的其实统计很方便.因为
A+B 与A+(2k+1)B 的第k 位是相同的.这样可以组成一个长度为
2k 的循环节,每个循环节里1 的个数一定是循环节长度的一半(2k−1 ).但是直接算完后可能会有余.
怎么办?
我们可以把一循环节里的一段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+A∼B+A∗N 范围内都是合法的.于是数位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]); }}
- 3492. 【NOIP2013模拟联考12】数数(count)(循环节/DP)
- 【NOIP2013模拟联考12】数数(数位dp||类欧几里得)
- 【JZOJ 3492】【NOIP2013模拟联考12】数数(count)
- [jzoj]3468. 【NOIP2013模拟联考7】OSU!(osu) (期望DP)
- [jzoj]3472. 【NOIP2013模拟联考8】匹配(match)(AC自动机+DP)
- jzoj3501 【NOIP2013模拟联考15】消息传递(news) 树形dp
- 【NOIP2013模拟联考13】线段
- 【NOIP2013模拟联考7】数列
- 【NOIP2013模拟联考5】军训
- 【NOIP2013模拟联考6】选课
- 【NOIP2013模拟联考7】OSU
- 3486. 【NOIP2013模拟联考10】道路改建(rebuild)(2017.12A组)(tarjan缩环+拓补排序+DP+bitset)
- SSL2864 【NOIP2013模拟联考15】物语(spfa优化)
- 【NOIP2013模拟联考5】军训(training)
- 【NOIP2013模拟联考6】选课(select)
- 【NOIP2013模拟联考5】军训(training) 题解
- NOIP2013模拟联考5】军训(training)
- 【NOIP2013模拟联考2】摘取作物(pick)
- Dagger2教程
- request.getContextPath()获取的是什么路径?
- 基于mvc模式的应用框架之struts(二)
- shell 替换不可见字符^@
- 大学生活随笔
- 3492. 【NOIP2013模拟联考12】数数(count)(循环节/DP)
- 性能测试新法宝:performance.now()
- 兄弟连学python(4)——列表、元祖、字典、集合数据类型介绍
- 一只程序猿的养成日记 第一章 第十二节 输入一个非负整数,返回组成它的数字之和
- GAN在网络表示中的应用--GraphGAN、Adversarial Network Embedding
- 使用TCP协议编写一个网络程序,设置服务器端的监听端口是8002,当与客户端建立连接后,服务器端向客户端发送数据“Hello, world”,客户端收到数据后打印输出。
- 一道数据结构的错题
- unity 鼠标移动 缩放,旋转
- xmind真正有用的几个快捷键(私人总结)