数位dp
bzoj3679 数字之积
题目大意:给定n、l、r,f(i)表示i的各位数字之积(不含前导0),求出0<f(i)<=n(l<=i<r)的个数。
思路:这是在夏令营中学长讲的题目,听到一种相对简单的方法。很显然要用1~r的个数-1~l的个数我们先用2、3、5、7(因为每一位都是0~9,所以只能有这几种质数乘起来)dfs出可能的乘积,放在一个数组num中,用f[i][j]表示i位数字(不含前导0)乘积在数组中的下表j的方案数,更新时,用能整除num[j]的k在num中的位置对应的f[i-1][pok]更新答案,然后把这个f数组做成前缀和,就表示下标对应数字<=j的个数了。然后就是数位的部分,把这个数分成一位一位的,首先把长度比这个数小的所有方案都加起来,然后考虑长度一样的,每次穷举严格小于这一位的数,找到可用的乘积在num中的位置,然后加给答案,每一位结束后,就将n除以这一位上的原数。这里有一个问题,就是0,如果这一位是0我们就直接break掉就可以了,因为后面不会有满足要求的答案了(乘起来肯定是0,不符合要求)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long using namespace std; LL num[100000]={0},f[20][100000]={0},ji[5]={0,2,3,5,7}; int nn[20]={0}; void dfs(int i,LL mul,LL n) { int j;LL mi=1; if (i>4) { if (mul<=n) num[++num[0]]=mul; return; } for (j=0;j<=50;++j) { dfs(i+1,mul*mi,n);mi*=ji[i]; if (mi*mul>n) break; } } LL work(LL n,LL m) { int i,j,po;LL x,ans=0; nn[0]=0;x=n; while(x){nn[++nn[0]]=x%10;x/=10;} if (nn[0]==0) return 0; for (i=1;i<nn[0];++i) ans+=f[i][num[0]]; for (i=nn[0];i;--i) { for (j=1;j<nn[i];++j) { po=upper_bound(num+1,num+num[0]+1,m/j)-num-1; ans+=f[i-1][po]; } if (nn[i]>0) m/=nn[i]; else break; } return ans; } int main() { int i,j,n,k,up=0; long long l,r,x; scanf("%d%I64d%I64d",&n,&l,&r); dfs(1,1,(LL)n);sort(num+1,num+num[0]+1); f[0][1]=1; for (i=1;i<=min(9,n);++i) f[1][i]=1; x=r;while(x){++up;x/=10;} for (i=2;i<=up;++i) for (j=1;j<=num[0];++j) for (k=1;k<=9;++k) { if ((int)num[j]%k) continue; int po=upper_bound(num+1,num+num[0]+1,num[j]/k)-num-1; f[i][j]+=f[i-1][po]; } for (i=0;i<=up;++i) for (j=1;j<=num[0];++j) f[i][j]+=f[i][j-1]; printf("%I64d\n",work(r,(LL)n)-work(l,(LL)n)); }
bzoj1026 windy数
题目大意:求a~b内各位数相差不小于2的数的个数。
思路:f[i][j]表示i位数开头为j(可以为0)的个数,g[i][j]表示i位数开头小于等于j的个数。然后查询x,x的位数为x0,答案中首先有所有位数比x0小的和与x0相同但第1位小于x第1位的,然后是从第2位到x0-1位的,跟上一位的相应的关系,但要注意这些都是比当前为小1的可以随便取,如果当前位和上一位不符合要求,就直接break。最后如果能扫到最后一位,然后对最后一位单独操作,因为最后一位能取到和这一位相同的。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int f[11][10]={0},xi[11]={0},g[11][10]={0}; int ab(int x){return x>0 ? x : -x;} int calc(int x) { int i,j,ans=0,cnt=0; xi[0]=0; if (x==0) return 0; while(x) { xi[++xi[0]]=x%10;x/=10; } for (i=1;i<xi[0];++i) ans+=g[i][9]-g[i][0]; ans+=g[xi[0]][xi[xi[0]]-1]-g[xi[0]][0]; for (i=xi[0]-1;i>1;--i) { if (xi[i]-1>=xi[i+1]+2) ans+=g[i][xi[i]-1]-g[i][xi[i+1]+1]; if (xi[i+1]-2>=0&&xi[i]>=1) ans+=g[i][min(xi[i+1]-2,xi[i]-1)]; if (ab(xi[i]-xi[i+1])<2) break; } if (xi[0]>=2&&i==1) { if (xi[1]>=xi[2]+2) ans+=g[1][xi[1]]-g[1][xi[2]+1]; if (xi[i+1]-2>=0) ans+=g[i][min(xi[i+1]-2,xi[i])]; } return ans; } int main() { int i,j,k,a,b; for (i=0;i<=9;++i) f[1][i]=1; g[1][0]=1; for (i=1;i<=9;++i) g[1][i]=g[1][i-1]+f[1][i]; for (i=2;i<=10;++i) { for (j=0;j<=9;++j) for (k=0;k<=9;++k) if (ab(j-k)>=2) f[i][j]+=f[i-1][k]; g[i][0]=f[i][0]; for (j=1;j<=9;++j) g[i][j]=g[i][j-1]+f[i][j]; } scanf("%d%d",&a,&b); printf("%d\n",calc(b)-calc(a-1)); }
bzoj1833 数字计数
题目大意:统计正整数a~b间每个数(0~9)出现的次数。
思路:f[i][j][t]表示长度为i,开头为j,t这个数字出现的次数。先dp预处理f数组(注意一个高位i上的数字不只是+1,而是加上10^i),然后数位dp,注意一些情况(对于一个高位上的还是不只+1)。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define maxnode 15 #define LL long long using namespace std; LL f[maxnode][10][10]={0},ans[10]={0}; int ai[maxnode]={0}; void calc(LL x,int kk) { int i,j,t; LL k,w; ai[0]=0;w=x;k=1; while(x){ai[++ai[0]]=x%10;x/=10;k*=10;} for (i=1;i<ai[0];++i) for (j=0;j<10;++j) for (t=1;t<10;++t) ans[j]+=f[i][t][j]*(LL)kk; for (i=ai[0];i>=1;--i) { for (j=(i==ai[0] ? 1 : 0);j<ai[i];++j) for (t=0;t<10;++t) ans[t]+=f[i][j][t]*(LL)kk; k/=10;ans[ai[i]]+=(w%k+1)*kk; } } int main() { int i,j,k,t;LL kk,a,b; kk=1; for (i=0;i<10;++i) f[1][i][i]=1; for (i=2;i<maxnode;++i) { kk*=(LL)10; for (j=0;j<10;++j) { for (k=0;k<10;++k) for (t=0;t<10;++t) f[i][j][t]+=f[i-1][k][t]; f[i][j][j]+=kk; } } scanf("%I64d%I64d",&a,&b);calc(a-1,-1);calc(b,1); for (i=0;i<9;++i) printf("%I64d ",ans[i]); printf("%I64d\n",ans[9]); }
hdu5435 A serious math problem
题目大意:求a~b之间所有数各位异或值的和。
思路:预处理i位数异或值为j的方案数为fi[i][j],然后数位dp就可以了。注意这里数的长度很长用字符串。还有数位dp的时候,每一位都从0~x[i]-1,就可以在一次循环里求出答案了(注意答案中还没有包含原数的异或值。)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define p 1000000007LL #define maxm 100005 #define LL long long using namespace std; LL fi[maxm][16]={0},ai[maxm]={0},bi[maxm]={0}; char s1[maxm],s2[maxm]; LL calc(LL *x){ LL sum=0;int i,j,k,t=0; if (x[0]==1&&x[1]==0) return 0; for (i=x[0];i;--i) t^=x[i]; sum=(sum+t)%p; for (t=0,i=x[0];i;--i){ for (j=0;j<16;++j) for (k=0;k<x[i];++k) sum=(sum+j*fi[i-1][t^j^k]%p)%p; t^=x[i]; }return sum; } int main(){ int t,i,j,k,l1,l2;scanf("%d",&t); fi[0][0]=1LL; for (i=1;i<maxm;++i) for (j=0;j<16;++j) for (k=0;k<10;++k) fi[i][k^j]=(fi[i][k^j]+fi[i-1][j])%p; for (k=1;k<=t;++k){ scanf("%s",&s1);scanf("%s",&s2); printf("Case #%d: ",k); ai[0]=l1=strlen(s1);bi[0]=l2=strlen(s2); for (i=0;i<l1;++i) ai[l1-i]=s1[i]-'0'; for (i=0;i<l2;++i) bi[l2-i]=s2[i]-'0'; if (ai[0]==1&&ai[1]==0) printf("%I64d\n",calc(bi)); else{ --ai[1]; for (i=1;i<ai[0];++i){ if (ai[i]<0){ai[i]+=10;--ai[i+1];} else break; }while(ai[0]>1&&ai[ai[0]]==0) --ai[0]; printf("%I64d\n",((calc(bi)-calc(ai))%p+p)%p); } } }
xjoi20 A
题目大意:求第q个逆序对数为k的n个元素的序列,每一位的顺序按给定的进行。
思路:先dp处理出fi[i][j]i位j个逆序对的个数(注意与inf取min,防止暴long long),然后一位一位的去做。
注意:每一位处理的时候和p的关系;数组大小。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 1005 #define maxx 10005 #define LL long long #define inf 2100000000LL using namespace std; LL fi[maxm][maxx]={0},sum[maxm][maxx]={0}; bool use[maxm]={false}; int cnt[maxm]={0},ans[maxm],rank[maxm][maxm]={0}; int main() { int n,k,i,j,p,x,up; bool f=false; scanf("%d%d%d",&n,&k,&p); fi[0][0]=fi[1][0]=1LL; for (i=0;i<=k;++i) sum[1][i]=1LL; for (i=2;i<=n;++i) for (j=0;j<=k;++j){ up=min(j,i-1); fi[i][j]=min(sum[i-1][j]-sum[i-1][j-up-1],inf); sum[i][j]=sum[i][j-1]+fi[i][j]; }if (fi[n][k]<p) printf("-1\n"); else{ for (i=1;i<=n;++i) for (j=1;j<=n;++j) scanf("%d",&rank[i][j]); for (i=1;i<=n;++i) cnt[i]=i-1; for (i=1;i<=n;++i){ for (j=1;j<=n;++j){ x=rank[i][j];if (use[x]) continue; if (k<cnt[x]) continue; if (fi[n-i][k-cnt[x]]<p){p-=fi[n-i][k-cnt[x]];continue;} if (fi[n-i][k-cnt[x]]>=p){ k-=cnt[x];ans[i]=x;use[x]=true; for (++x;x<=n;++x) --cnt[x]; break; } }if (j>n){f=true;break;} }if (f) printf("-1\n"); else{ for (i=1;i<n;++i) printf("%d ",ans[i]); printf("%d\n",ans[n]); } } }
poj1037 A decorative fence
题目大意:求字典序第k的抖动序列。
思路:fi[i][j][0]表示长度为i,以j开头前面下降的个数;fi[i][j][1]表示长度为i,以j开头前面上升的个数。然后像数位dp那样每一次找一下,注意第一位可以上升也可以下降。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 25 #define up 20 #define LL long long using namespace std; LL fi[maxm][maxm][2]={0}; int ans[maxm]={0},cnt[maxm]={0}; bool vi[maxm]={0}; int main(){ int i,j,k,n,t,fu;LL m; scanf("%d",&t); memset(fi,0,sizeof(fi)); fi[1][1][0]=fi[1][1][1]=1; for (i=2;i<=up;++i) for (j=1;j<=up;++j){ for (k=1;k<j;++k) fi[i][j][0]+=fi[i-1][k][1]; for (k=j;k<i;++k) fi[i][j][1]+=fi[i-1][k][0]; } while(t--){ scanf("%d%I64d",&n,&m);fu=0; for (i=1;i<=n;++i) cnt[i]=i; memset(vi,false,sizeof(vi)); for (i=1;i<=n;++i){ fu^=1; for (j=1;j<=n;++j){ if (vi[j]) continue; if (i==1){ if (m-fi[n-i+1][cnt[j]][0]-fi[n-i+1][cnt[j]][1]>0){ m-=fi[n-i+1][cnt[j]][0]+fi[n-i+1][cnt[j]][1]; }else{ if (m-fi[n-i+1][cnt[j]][0]>0){m-=fi[n-i+1][cnt[j]][0];fu=1;} else fu=0; break; } }else{ if (fu&&j>=ans[i-1]) continue; if (!fu&&j<=ans[i-1]) continue; if (m-fi[n-i+1][cnt[j]][fu]>0) m-=fi[n-i+1][cnt[j]][fu]; else break; } }if (j<=n){ ans[i]=j;vi[j]=true; for (++j;j<=n;++j) --cnt[j]; } }for (i=1;i<n;++i) printf("%d ",ans[i]); printf("%d\n",ans[n]); } }
bzoj3652 bignews(!!!)
题目大意:给定n,q(未加密的可能性)。加密的话就是求x(0~n-1)^y(0~n-1)的期望,未加密的就是求x(0~n-1)^y(0~n-1)(y取能使这个最大的值)的期望。
思路:对于加密的求出每一位为1的概率,然后就是sigma2^i*2*x/n*(1-x)/n,对于未加密的,fi[i][j][k]表示到第i位(高位-低位),x、y的限制分别为j、k(0表示之前的小于,1表示之前的卡着上界),然后转移一下,这里用的是上一位的状态求出这一位的状态来更新。其实加密的也可以用未加密的套路来求。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define up 70 using namespace std; int ai[up]={0}; LL mi[up]={0LL},ci[up]={0LL}; double bi[up],fi[up][2][2]={0},gi[up][2][2]={0}; double calc0(LL n){ int i,j,k,a,b,k1,k2;double ans=0.0,per=1.0/(double)n; LL x=n-1;ai[0]=0; while(x){ ai[++ai[0]]=(int)(x%2);x/=2; }gi[ai[0]+1][1][1]=per; for (i=ai[0];i;--i) for (j=0;j<=1;++j) for (k=0;k<=1;++k) if (gi[i+1][j][k]>0.0) for (a=0;a<=1;++a){ if (j){ if (a<ai[i]) k1=0; else{ if (a==ai[i]) k1=1; else continue; } }else k1=0; if (k){ b=a^1; if (b>ai[i]) b^=1; if (b<ai[i]) k2=0; else k2=1; }else{b=a^1;k2=0;} fi[i][k1][k2]+=fi[i+1][j][k]+mi[i-1]*gi[i+1][j][k]*(a^b); gi[i][k1][k2]+=gi[i+1][j][k]; } for (i=0;i<=1;++i) for (j=0;j<=1;++j) ans+=fi[1][i][j]; return ans; } double calc1(LL n){ int i,j;LL x=n;double ans=0.0; for (i=ai[0];i;--i){ if (ai[i]){ n-=mi[i-1];ci[i]+=n; for (j=i-1;j;--j) ci[j]+=(i-2>=0 ? mi[i-2] : 0LL); } }for (n=x,i=ai[0];i;--i) ans+=(double)ci[i]/(double)n*(double)(n-ci[i])/(double)n*2.0*mi[i-1]; return ans; } int main(){ LL x,n;int i;double p; scanf("%I64d%lf",&n,&p);x=n; while(n){ ai[++ai[0]]=(int)(n%2);n/=2; }mi[0]=1LL;n=x; for(i=1;i<=ai[0];++i) mi[i]=mi[i-1]*2LL; printf("%.6f\n",calc0(n)*p+calc1(n)*(1.0-p)); }
bc73 b
题目大意:求1<=i<j<=n且i的二进制中1个数比j的大的数对个数。
思路:fi[i][j][a][b]表示前i位、数位差是j-N、两数大小关系、大数和n大小关系。枚举转移。初始化的时候fi[0][0+N][1][1]=1。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1005 #define p 998244353 using namespace std; char ss[N]; int num[N],len,ai[N],al=0,fi[N][N<<1][2][2]={0};//前i位、位数差j、两数大小、大数和n大小 bool zero(){return (len==1&&num[1]==0);} void chu(){ int i,g=0; for (i=len;i;--i){ g=g*10+num[i]; num[i]=g/2;g=g%2; }while(len>1&&num[len]==0) --len;} void jia(int &x,int y){x=(x+y)%p;} int dp(){ int i,j,a,b,k1,k2,c,d,ans=0; for (i=al/2;i;--i) swap(ai[i],ai[al-i+1]); memset(fi,0,sizeof(fi)); fi[0][N][1][1]=1; for (i=0;i<al;++i) for (j=0;j<=(N<<1);++j) for (a=0;a<=1;++a) for (b=0;b<=1;++b){ if (!fi[i][j][a][b]) continue; for (k1=0;k1<=1;++k1) for (k2=0;k2<=1;++k2){ if (!a) c=0; else{ if (k1<k2) continue; if (k1==k2) c=1; else c=0; }if (!b) d=0; else{ if (k1>ai[i+1]) continue; if (k1==ai[i+1]) d=1; else d=0; }jia(fi[i+1][j+k1-k2][c][d],fi[i][j][a][b]); } } for (j=0;j<N;++j) for (b=0;b<=1;++b) jia(ans,fi[al][j][0][b]); return ans;} int main(){ int i,t;scanf("%d",&t); while(t--){ scanf("%s",ss);len=strlen(ss);al=0; for (i=len;i;--i) num[i]=ss[len-i]-'0'; while(!zero()){ ai[++al]=num[1]%2;chu(); }printf("%d\n",dp()); } }
bzoj3131 淘金
题目大意:给定一个N*N的网格,每个位置有一个金子,(i,j)->(fi,fj),fx是x各位数乘积。问一次变化后网格中金子最多的k个的和。(N<=10^12)
思路:数位dp,1~N的fi出现几次,因为fi的取值很少,可以用map存下来,暴力dp转移(高位的与原数相等和高位的小于原数)。统计答案相当于已知一个序列,问两两乘积的前k大的和,用堆维护一下。(如果k很大可以二分)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #include<queue> #define N 100005 #define up 10 #define LL long long #define p 1000000007LL using namespace std; struct use{ LL v;int bp,po; bool operator<(const use&x)const{return v<x.v;} }; map<LL,LL>ct1[2],ct2[2]; map<LL,LL>::iterator it,en; int cur,ai[N],bz=0; LL bi[N]; priority_queue<use> que; void pre(LL x){ int i,j;LL xx=x;ai[0]=0; while(xx){ ai[++ai[0]]=(int)(xx%10LL); xx/=10LL; }cur=0; for (i=ai[ai[0]]-1;i;--i) ct1[cur][(LL)i]=1LL; ct2[cur][(LL)ai[ai[0]]]=1LL;++ct1[cur][1LL]; for (i=ai[0]-1;i;--i){ cur^=1;ct1[cur].clear();ct2[cur].clear(); for (en=ct1[cur^1].end(),it=ct1[cur^1].begin();it!=en;++it) for (j=1;j<up;++j) ct1[cur][(LL)j * it->first]+=it->second; for (en=ct2[cur^1].end(),it=ct2[cur^1].begin();it!=en;++it){ for (j=1;j<ai[i];++j) ct1[cur][(LL)j * it->first]+=it->second; ct2[cur][(LL)ai[i] * it->first]+=it->second; }++ct1[cur][1LL]; }for (en=ct2[cur].end(),it=ct2[cur].begin();it!=en;++it) ct1[cur][it->first]+=it->second; --ct1[cur][1]; for (en=ct1[cur].end(),it=ct1[cur].begin();it!=en;++it){ if (it->first > x) break; if (it->first == 0LL) continue; bi[++bz]=it->second; }sort(bi+1,bi+bz+1); for (i=1;i<=bz;++i) que.push((use){bi[i]*bi[bz],i,bz}); } LL work(int k){ LL ans=0LL;use cc;k=min(k,bz*bz); while(k--){ cc=que.top();que.pop(); ans=(ans+cc.v)%p; if (cc.po>1) que.push((use){bi[cc.bp]*bi[cc.po-1],cc.bp,cc.po-1}); }return ans;} int main(){ LL n;int k; scanf("%I64d%d",&n,&k); pre(n); printf("%I64d\n",work(k)); }
bzoj3802&&4043 Vocabulary
题目大意:三个字符串,由小写字母和?组成,?可以代表任意一个小写字母,三个长度不一定相同。问三个字典序升序的方案数。
思路:数位dp,fi[i][j]表示长度为i,三个串到这个位置的关系是j的方案数。j=0表示a=b=c;j=1表示a=b<c,j=2表示a<b=c,j=3表示a<b<c的,fi[ml][3]是答案。可以预处理出26的幂,x个数中选2个的、x个数中选3个的组合数,更新的时候根据i位上的字母关系更新。
注意:1)如果递推预处理,要注意更新的方程;
2)转移的方案很多,很复杂。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1000005 #define up 3 #define mz 26 #define LL long long #define p 1000000009LL using namespace std; char ss[N]; int li[up],ml=0,ai[up][N]; LL gi[mz+1]={0LL},fi[N][4]={0LL},zi[mz+1]={0LL},mi[mz+1]; int idx(int i){ int j,u=0; for (j=0;j<up;++j) if (ai[j][i]<mz) u=u|(1<<j); return u;} inline void add(LL &a,LL b){a=(a+b)%p;} LL dp(){ int i,j,u,x,y,z; fi[0][0]=1LL; for (i=0;i<ml;++i){ for (u=0,j=0;j<up;++j){ if (ai[j][i]==N) continue; if (u==0) u=ai[j][i]+2; else if (u!=ai[j][i]+2) break; }if (j>=up){ if (u==0) add(fi[i+1][0],fi[i][0]*(LL)mz%p); else add(fi[i+1][0],fi[i][0]*(LL)(u==1 ? 0 : 1)); }u=idx(i); if (u==0){ add(fi[i+1][1],(fi[i][0]*zi[mz]%p+fi[i][1]*mi[2]%p)%p); add(fi[i+1][2],(fi[i][0]*zi[mz]%p+fi[i][2]*mi[2]%p)%p); add(fi[i+1][3],(fi[i][0]*gi[mz]%p+fi[i][1]*zi[mz]*(LL)mz%p+ fi[i][2]*zi[mz]*(LL)mz%p+fi[i][3]*mi[3]%p)%p); continue; }if (u==1){ x=ai[0][i]+1; add(fi[i+1][1],(fi[i][0]*(LL)(x?(mz-x):0)%p+fi[i][1]*(LL)(x?mz:0)%p)%p); add(fi[i+1][2],(fi[i][0]*(LL)(mz-x)%p+fi[i][2]*(LL)mz%p)%p); add(fi[i+1][3],(fi[i][0]*zi[mz-x]%p+fi[i][1]*(LL)mz%p*(LL)(mz-x)%p+ fi[i][2]*zi[mz]%p+fi[i][3]*mi[2]%p)%p); continue; }if (u==2){ x=ai[1][i]+1; add(fi[i+1][1],(fi[i][0]*(LL)(x?(mz-x):0)%p+fi[i][1]*(LL)(x?mz:0)%p)%p); add(fi[i+1][2],(fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][2]*(LL)(x?mz:0)%p)%p); add(fi[i+1][3],(fi[i][0]*(LL)(x?(x-1)*(mz-x):0)%p+fi[i][1]*(LL)(x?(x-1)*mz:0)%p+ fi[i][2]*(LL)(mz*(mz-x))%p+fi[i][3]*mi[2]%p)%p); continue; }if (u==4){ x=ai[2][i]+1; add(fi[i+1][1],(fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][1]*(LL)mz%p)%p); add(fi[i+1][2],(fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][2]*(LL)(x?mz:0)%p)%p); add(fi[i+1][3],(fi[i][0]*(LL)(x?zi[x-1]:0)%p+fi[i][1]*(LL)zi[mz]%p+ fi[i][2]*(LL)(x?mz*(x-1):0)%p+fi[i][3]*mi[2]%p)%p); continue; }if (u==3){ x=ai[0][i]+1;y=ai[1][i]+1; if (x==y){ add(fi[i+1][1],(fi[i][0]*(LL)(mz-x)%p+fi[i][1]*(LL)mz%p)%p); add(fi[i+1][2],fi[i][2]*(LL)(x?1:0)); add(fi[i+1][3],(fi[i][2]*(LL)(mz-x)%p+fi[i][3]*(LL)mz%p)%p); }if (x>y){ add(fi[i+1][2],(fi[i][2]*(LL)(y?1:0)%p)); add(fi[i+1][3],(fi[i][2]*(LL)(mz-y)%p+fi[i][3]*(LL)mz%p)%p); }if (x<y){ add(fi[i+1][2],(fi[i][0]+fi[i][2])%p); add(fi[i+1][3],(fi[i][0]*(LL)(mz-y)%p+fi[i][1]*(LL)mz%p+ fi[i][2]*(LL)(mz-y)%p+fi[i][3]*(LL)mz%p)%p); }continue; }if (u==5){ x=ai[0][i]+1;y=ai[2][i]+1; if (x==y){ add(fi[i+1][1],fi[i][1]*(LL)(x?1:0)); add(fi[i+1][2],fi[i][2]*(LL)(x?1:0)); add(fi[i+1][3],(fi[i][1]*(LL)(mz-x)%p+fi[i][2]*(LL)(x?(x-1):0)%p+ fi[i][3]*(LL)mz)%p); }if (x>y){ add(fi[i+1][1],fi[i][1]); add(fi[i+1][2],fi[i][2]*(LL)(y?1:0)); add(fi[i+1][3],(fi[i][1]*(LL)(mz-x)%p+fi[i][2]*(LL)(y?(y-1):0)%p+ fi[i][3]*(LL)mz%p)%p); }if (x<y){ add(fi[i+1][1],(fi[i][0]*(LL)(x?1:0)+fi[i][1]*(LL)(x?1:0))%p); add(fi[i+1][2],(fi[i][0]+fi[i][2])%p); add(fi[i+1][3],(fi[i][0]*(LL)(y-x-1)%p+fi[i][1]*(LL)(mz-x)%p+ fi[i][2]*(LL)(y-1)%p+fi[i][3]*mz%p)%p); }continue; }if (u==6){ x=ai[1][i]+1;y=ai[2][i]+1; if (x==y){ add(fi[i+1][1],fi[i][1]*(LL)(x?1:0)%p); add(fi[i+1][2],fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][2]*(LL)mz%p); add(fi[i+1][3],(fi[i][1]*(LL)(x?(x-1):0)%p+fi[i][3]*(LL)mz%p)%p); }if (x<y){ add(fi[i+1][1],(fi[i][0]*(LL)(x?1:0)+fi[i][1]*(LL)(x?1:0))%p); add(fi[i+1][3],(fi[i][0]*(LL)(x?(x-1):0)%p+fi[i][1]*(LL)(x?(x-1):0)%p+ fi[i][2]*(LL)mz%p+fi[i][3]*mz%p)%p); }if (x>y){ add(fi[i+1][1],fi[i][1]); add(fi[i+1][3],(fi[i][1]*(LL)(x-1)%p+fi[i][3]*(LL)mz)%p); }continue; }if (u==7){ x=ai[0][i]+1;y=ai[1][i]+1;z=ai[2][i]+1; if (x==y&&y<z) add(fi[i+1][1],fi[i][0]); if (x==y) add(fi[i+1][1],fi[i][1]); if (x<y&&y==z) add(fi[i+1][2],fi[i][0]); if (y==z) add(fi[i+1][2],fi[i][2]); if (x<y&&y<z) add(fi[i+1][3],fi[i][0]); if (x<y) add(fi[i+1][3],fi[i][1]); if (y<z) add(fi[i+1][3],fi[i][2]); add(fi[i+1][3],fi[i][3]); } }return fi[ml][3]; } int main(){ int i,j,t;scanf("%d",&t); for (mi[0]=1LL,i=1;i<=mz;++i){ zi[i]=(LL)i*(LL)(i-1)/2LL; gi[i]=(gi[i-1]+zi[i-1])%p; mi[i]=mi[i-1]*(LL)mz%p; }while(t--){ for (i=0;i<=ml;++i) for (j=0;j<4;++j) fi[i][j]=0LL; for (ml=i=0;i<up;++i){ scanf("%s",ss);li[i]=strlen(ss); for (j=0;j<li[i];++j) ai[i][j]=(ss[j]=='?' ? N : ss[j]-'a'); ml=max(ml,li[i]); }for (i=0;i<up;++i) for (j=li[i];j<ml;++j) ai[i][j]=-1; printf("%I64d\n",dp()); } }
(还可以暴力预处理出所有状态间转移的矩阵,然后转移的时候直接用矩阵就可以了。)
bzoj3209 花神的数论题
题目大意:sum[i]表示i的二进制中1的个数,求∏(i=1~n)sum[i]。(n<=10^15)
思路:数位dp。没有限制的时候,二进制长度小于等于i的贡献是∏(j=1~i)j^c(i,j)。对n的二进制位从高到低考虑,对于是1的这一位,之前都选1,这一位选0的时候算出贡献。最后*sum[n]。
注意:组合数不会爆longlong,但如果要取模的话,因为它是指数,所以应该%phi(p),而这里的p=10000007不是质数,所以要处理。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define N 51 #define p 10000007LL using namespace std; int ai[N],az=0; LL c[N][N]={0LL}; LL mi(LL x,LL y){ LL a=1LL; for (;y;y>>=1LL){ if (y&1LL) a=a*x%p; x=x*x%p; }return a;} int main(){ LL n,bin,ans=1LL;int i,j,cnt=0; for (i=0;i<N;++i) for (c[i][0]=1LL,j=1;j<=i;++j) c[i][j]=c[i-1][j-1]+c[i-1][j]; scanf("%I64d",&n); for (;n;n>>=1LL) ai[++az]=(int)(n&1LL); for (cnt=0;az;--az){ if (!ai[az]) continue; for (i=0;i<az;++i) if (cnt+i) ans=ans*mi((LL)(cnt+i),c[az-1][i])%p; ++cnt; }ans=ans*cnt%p; printf("%I64d\n",ans); }
bzoj2757 Blinker的仰慕者
题目大意:求a~b中各位乘积为k的数的和。
思路:k=0的时候fi[i][j][a][b]表示i位、和x关系j、前面有无非0的数、乘积是不是0的数的个数,ii[][][][]表示和,暴力转移;k!=0的时候这样转移会tle,考虑用最早写过的dp方法,预处理i位长度时乘积为j的个数和和(j的个数十分有限),从高到低卡着dp。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #define N 20 #define p 20120427LL #define M 4 #define LL long long #define nn 100000 #define len 1000000 using namespace std; int pr[M]={2,3,5,7},dt[N]={0}; LL ai[N],di[N][nn],ci[len],fi[N][2][2][2],ii[N][2][2][2],gi[N][nn],hi[N][nn],mi[N]; void add(LL &x,LL y){x=(x+y)%p;} void pre(){ int i,j,k,a,cz;LL b; memset(gi,0,sizeof(gi)); memset(hi,0,sizeof(hi)); di[0][++dt[0]]=1LL; gi[0][dt[0]]=1LL; for (mi[0]=1LL,i=1;i<N;++i) mi[i]=mi[i-1]*10LL%p; for (i=0;i<N-2;++i){ for (cz=0,j=1;j<=dt[i];++j) for (k=1;k<=9;++k) ci[++cz]=di[i][j]*(LL)k; sort(ci+1,ci+cz+1);cz=unique(ci+1,ci+cz+1)-ci-1; for (dt[i+1]=cz,j=1;j<=cz;++j) di[i+1][j]=ci[j]; for (j=1;j<=dt[i];++j) for (k=1;k<=9;++k){ b=di[i][j]*(LL)k; a=upper_bound(ci+1,ci+cz+1,b)-ci-1; add(gi[i+1][a],gi[i][j]); add(hi[i+1][a],(gi[i][j]*(LL)k+hi[i][j]*10LL)%p); } } } LL getf(LL x){ int n,i,j,a,b,c,nj,na,nb;LL k,kk; for (n=0;x;x/=10LL) ai[++n]=(int)(x%10LL); for (i=1;(i<<1)<=n;++i) swap(ai[i],ai[n-i+1]); memset(fi,0,sizeof(fi)); memset(ii,0,sizeof(ii)); fi[0][1][0][0]=1LL; for (i=0;i<n;++i) for (j=0;j<2;++j) for (a=0;a<2;++a) for (b=0;b<2;++b){ if (!fi[i][j][a][b]&&!ii[i][j][a][b]) continue; k=fi[i][j][a][b]; kk=ii[i][j][a][b]; for (c=0;c<=9;++c){ nj=0; if (j){ if (c>ai[i+1]) continue; if (c==ai[i+1]) nj=1; }na=a;nb=b; if (c) na=1; if (a&&!c) nb=1; add(fi[i+1][nj][na][nb],k); add(ii[i+1][nj][na][nb],(kk*10LL+(LL)c*k)%p); } } return (ii[n][0][1][1]+ii[n][1][1][1])%p; } LL geth(int i,LL k){ int a=upper_bound(di[i]+1,di[i]+dt[i]+1,k)-di[i]-1; if (di[i][a]!=k) return 0LL; return hi[i][a];} LL getg(int i,LL k){ int a=upper_bound(di[i]+1,di[i]+dt[i]+1,k)-di[i]-1; if (di[i][a]!=k) return 0LL; return gi[i][a];} LL getg(LL x,LL k){ int n,i,j;LL ans=0LL,cc=0LL; if (!x) return 0;++x; for (n=0;x;x/=10LL) ai[++n]=(int)(x%10LL); for (i=1;(i<<1)<=n;++i) swap(ai[i],ai[n-i+1]); for (i=1;i<n;++i) ans=(ans+geth(i,k))%p; for (i=1;i<=n;++i){ if (!ai[i]) break; for (j=1;j<ai[i];++j) if (k%(LL)j==0LL){ ans=(ans+geth(n-i,k/j)+getg(n-i,k/j)*(cc*10LL%p+(LL)j)%p*mi[n-i])%p; } if (k%(LL)ai[i]) break; k/=(LL)ai[i];cc=cc*10LL+ai[i]; }return ans;} int main(){ int i,n;LL a,b,k,kk; scanf("%d",&n);pre(); while(n--){ scanf("%I64d%I64d%I64d",&a,&b,&k); if (!k){printf("%I64d\n",((getf(b)-getf(a-1LL))%p+p)%p);continue;} for (kk=k,i=0;i<M;++i) while(kk%pr[i]==0) kk/=pr[i]; if (kk>1) printf("0\n"); else printf("%I64d\n",((getg(b,k)-getg(a-1LL,k))%p+p)%p); } }
省队集训 number(!!!)
题目大意:给出n,每次可以减去n某个数位上的数字,问n最少几次变为0。
思路:每次都是减去最大的那个数最优,并且肯定是999...i到负数的过程,预处理fi[i][j][k]表示i位数,前i-1位是9,个位数是j,i位之前的数的最大值是k,减到后i位为负数的步数,gi[i][j][k]表示减成的负数。求答案的时候,先从低位到高位消成999...i的形式,再从高位算下来,最后如果个位非0,还要+1。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 20 #define up 10 #define LL long long using namespace std; LL fi[N][N][up]; int gi[N][N][up],ai[N]={0}; void pre(){ int i,j,k,a; memset(fi,0,sizeof(fi)); for (j=0;j<up;++j) for (k=0;k<up;++k){ if (j<k){ fi[1][j][k]=1LL; gi[1][j][k]=10+j-k; }else{ fi[1][j][k]=2LL; gi[1][j][k]=10-k; } } for (i=2;i<N;++i){ for (j=0;j<up;++j) for (k=0;k<up;++k){ gi[i][j][k]=j; for (a=up-1;a>=0;--a){ fi[i][j][k]+=fi[i-1][gi[i][j][k]][max(a,k)]; gi[i][j][k]=gi[i-1][gi[i][j][k]][max(a,k)]; } } } } LL dp(LL n){ int i,j,ci,cc;LL ans=0LL; for (;n;n/=10LL) ai[++ai[0]]=(int)(n%10LL); ci=ai[1]; for (i=2;i<=ai[0];++i) while(ai[i]!=9){ for (cc=0,j=i;j<=ai[0];++j) cc=max(cc,ai[j]); if (!cc) break; ans+=fi[i-1][ci][cc]; ci=gi[i-1][ci][cc]; --ai[i]; for (j=i;ai[j]<0;++j){ai[j]+=10;--ai[j+1];} } for (i=ai[0];i>=2;--i){ while(ai[i]){ ans+=fi[i-1][ci][ai[i]]; ci=gi[i-1][ci][ai[i]]; --ai[i]; } }return ans+(ci!=0); } int main(){ freopen("number.in","r",stdin); freopen("number.out","w",stdout); LL n;pre(); scanf("%I64d",&n); printf("%I64d\n",dp(n)); }
poj3899 The Lucky Numbers
题目大意:认为只包含4和7的数字是幸运数字,求[a,b]内的幸运数字的个数+x是幸运数字且x在[a,b]但x翻转过来不再[a,b]内的个数(翻转,如:447变为744)。
思路:进行三次数位dp,分别求出[a,b]内幸运数字个数、x在[a,b]内翻转后在[b+1,)内、x在[a,b]内翻转后在[0,a-1]内。可以用上面的数位dp,相应表示大小关系,求幸运数字的时候可能包含前导0,其他的两个因为翻转和范围的问题所以没有前导0。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 50 #define up 10 using namespace std; struct use{ int l,nm[N]; void clear(){memset(nm,0,sizeof(nm));l=1;} bool zero(){return (l==1&&nm[1]==0);} use operator+(const use&x)const{ int a,b,i,g=0;use c;c.l=max(l,x.l); for (i=1;i<=c.l;++i){ a=(i>l ? 0 : nm[i]); b=(i>x.l ? 0 : x.nm[i]); c.nm[i]=a+b+g; g=c.nm[i]/10; c.nm[i]%=10; }while(g){c.nm[++c.l]=g%10;g/=10;} return c; } use operator-(const use&x)const{ int b,i;use c;c.l=l;c.nm[1]=0; for (i=1;i<=c.l;++i){ b=(i>x.l ? 0 : x.nm[i]); c.nm[i]=c.nm[i]+nm[i]-b; if (c.nm[i]<0){ c.nm[i]+=10; c.nm[i+1]=-1; }else c.nm[i+1]=0; }while(c.l>1&&!c.nm[c.l]) --c.l; return c; } }fi[N][2][2],ai,bi,per; char ss[N]; use in(){ int i;use ci;scanf("%s",ss); ci.l=strlen(ss); for (i=ci.l;i;--i) ci.nm[i]=ss[ci.l-i]-'0'; return ci;} void add(use &x,use y){x=x+y;} void print(use x){ for (int i=x.l;i;--i) printf("%d",x.nm[i]); printf("\n"); } use dp(use x){ int i,j,a,b,na,nb,xx; for (i=0;i<N;++i) for (a=0;a<2;++a) for (b=0;b<2;++b) fi[i][a][b].clear(); fi[0][1][0].nm[1]=1; for (i=0;i<x.l;++i){ xx=x.nm[x.l-i]; for (a=0;a<2;++a) for (b=0;b<2;++b){ if (fi[i][a][b].zero()) continue; for (j=0;j<up;++j){ if (j&&j!=4&&j!=7) continue; if (a){ if (j>xx) continue; na=(j==xx); }else na=0; if (!j&&b) continue; nb=(j>0); add(fi[i+1][na][nb],fi[i][a][b]); } } }return fi[x.l][0][1]+fi[x.l][1][1]; } use cal(use l,use r){ int ll,rr,i,j,a,b,na,nb; for (i=0;i<N;++i) for (a=0;a<2;++a) for (b=0;b<2;++b) fi[i][a][b].clear(); fi[0][1][0].nm[1]=1; for (i=0;i<r.l;++i){ ll=(r.l-i>l.l ? 0 : l.nm[r.l-i]); rr=r.nm[i+1]; for (a=0;a<2;++a) for (b=0;b<2;++b){ if (fi[i][a][b].zero()) continue; for (j=0;j<up;++j){ if (j!=4&&j!=7) continue; if (a){ if (j>ll) continue; na=(j==ll); }else na=0; if (j<rr) nb=0; if (j==rr) nb=b; if (j>rr) nb=1; add(fi[i+1][na][nb],fi[i][a][b]); } } }return fi[r.l][0][1]+fi[r.l][1][1]; } use dp2(use l,use r){ int ll,rr,i,j,a,b,na,nb; for (i=0;i<N;++i) for (a=0;a<2;++a) for (b=0;b<2;++b) fi[i][a][b].clear(); if (l.l>r.l) fi[0][0][1].nm[1]=1; else fi[0][1][1].nm[1]=1; for (i=0;i<r.l;++i){ ll=l.nm[r.l-i]; rr=r.nm[i+1]; for (a=0;a<2;++a) for (b=0;b<2;++b){ if (fi[i][a][b].zero()) continue; for (j=0;j<up;++j){ if (j!=4&&j!=7) continue; if (a){ if (j>ll) continue; na=(j==ll); }else na=0; if (j<rr) nb=0; if (j==rr) nb=b; if (j>rr) nb=1; add(fi[i+1][na][nb],fi[i][a][b]); } } }return fi[r.l][0][0]+fi[r.l][1][0]; } int main(){ int t;scanf("%d",&t); per.l=per.nm[1]=1; while(t--){ ai=in();bi=in(); print(dp(bi)-dp(ai-per)+cal(bi,bi)-cal(ai-per,bi)+dp2(bi,ai-per)-dp2(ai-per,ai-per)); } }