bzoj2757

非常神的数位dp,我调了几乎一天
首先和bzoj3131类似,乘积是可以预处理出来的,注意这里乘积有一个表示的技巧
因为这里质因数只有2,3,5,7,所以我们可以表示成2^a*3^b*5^c*7^d,也就是v[a,b,c,d]这个数组
这样也非常方便进行除法,乘法的状态转移,转移和要维护的东西比较多,我就直接在程序里注释了
这里维护转移的核心思想是算每一位对编号和的贡献,乘积为0和非0分开计算

  1 const mo=20120427;
  2       lim=1000000000000000000;
  3       q:array[1..9,1..4] of longint=((0,0,0,0),(1,0,0,0),(0,1,0,0),(2,0,0,0),(0,0,1,0),
  4                                      (1,1,0,0),(0,0,0,1),(3,0,0,0),(0,2,0,0));
  5 //1~9对乘积的影响
  6 var v:array[0..60,0..38,0..30,0..22] of longint;
  7     f,s:array[0..20,0..70010] of longint;
  8     p,r,g,h:array[0..20] of longint;
  9     pp:array[0..20] of int64;
 10     n,a,b,c,d,i,j,t,m,k:longint;
 11     x,y,w:int64;
 12 
 13 procedure up(var a:longint; b:longint);
 14   begin
 15     a:=(a+b+mo) mod mo;
 16   end;
 17 
 18 procedure pre;
 19   var i,j,k,p:int64;
 20       a,b,c,d:longint;
 21   begin
 22     i:=1;
 23     a:=0;
 24     while i<=lim do
 25     begin
 26       j:=i; b:=0;
 27       while j<=lim do
 28       begin
 29         k:=j; c:=0;
 30         while k<=lim do
 31         begin
 32           p:=k; d:=0;
 33           while p<=lim do
 34           begin
 35             inc(t);
 36             v[a,b,c,d]:=t;
 37             p:=p*7;
 38             inc(d);
 39           end;
 40           k:=k*5;
 41           inc(c);
 42         end;
 43         j:=j*3;
 44         inc(b);
 45       end;
 46       i:=i*2;
 47       inc(a);
 48     end;
 49   end;
 50 
 51 procedure work;
 52   var a,b,c,d,i,j:longint;
 53   begin
 54     f[0,1]:=1;
 55     for i:=1 to 18 do
 56       for a:=0 to 60 do for b:=0 to 38 do for c:=0 to 26 do for d:=0 to 22 do
 57         if (v[a,b,c,d]>0) and (f[i-1,v[a,b,c,d]]>0) then
 58         begin
 59           for j:=1 to 9 do
 60           begin
 61             up(f[i,v[a+q[j,1],b+q[j,2],c+q[j,3],d+q[j,4]]],f[i-1,v[a,b,c,d]]);
 62 //f[i,S]表示i位数乘积为S的数的个数
 63             up(s[i,v[a+q[j,1],b+q[j,2],c+q[j,3],d+q[j,4]]],s[i-1,v[a,b,c,d]]+int64(f[i-1,v[a,b,c,d]])*int64(j)*int64(p[i]) mod mo);
 64 //s[i,S]表示i位数乘积为S的数的和,转移就是算当前第i位对和的贡献,显然是j(第i位的数)*p[i](位权)*f[i-1,S/j]
 65           end;
 66         end
 67         else if v[a,b,c,d]=0 then break;
 68     h[0]:=1;
 69     for i:=1 to 18 do
 70     begin
 71       h[i]:=h[i-1]*9 mod mo;  //i位数不含0的个数
 72       g[i]:=(g[i-1]*9+45*int64(p[i])*int64(h[i-1]) mod mo) mod mo;  //i位数不含0的数的和,45=1+..+9
 73     end;
 74   end;
 75 
 76 procedure get(x:int64);
 77   begin
 78     m:=0;
 79     while x>0 do
 80     begin
 81       inc(m);
 82       r[m]:=x mod 10;
 83       x:=x div 10;
 84     end;
 85   end;
 86 
 87 function can(x:int64):boolean;
 88   begin
 89     a:=0; b:=0; c:=0; d:=0;  //拆分成2^a*3^b*5^c*7^d
 90     while x mod 2=0 do
 91     begin
 92       inc(a);
 93       x:=x div 2;
 94     end;
 95     while x mod 3=0 do
 96     begin
 97       inc(b);
 98       x:=x div 3;
 99     end;
100     while x mod 5=0 do
101     begin
102       inc(c);
103       x:=x div 5;
104     end;
105     while x mod 7=0 do
106     begin
107       inc(d);
108       x:=x div 7;
109     end;
110     if x=1 then exit(true) else exit(false);
111   end;
112 
113 function sum(a:int64):longint;  //防止爆int64的计算
114   var b:int64;
115   begin
116     b:=a-1;
117     if a mod 2=0 then a:=a div 2
118     else b:=b div 2;
119     a:=a mod mo;
120     b:=b mod mo;
121     exit(a*b mod mo);
122   end;
123 
124 function count(x,k:int64):longint;
125   var i,j,pre,a1,b1,c1,d1,ans:longint;
126       now:int64;
127   begin
128     if not can(k) then exit(0);  //k不存在
129     get(x);
130     ans:=0;
131     for i:=1 to m-1 do  //统计1~m-1位数乘积为k数的和
132       up(ans,s[i,v[a,b,c,d]]);
133     now:=1;
134     pre:=0;  //pre=∑(r[i]*p[i])
135     for i:=m downto 1 do
136     begin
137       now:=now*int64(r[i]); 
138       if r[i]=0 then break; //显然
139       for j:=1 to r[i]-1 do
140       begin
141         a1:=a-q[j,1]; b1:=b-q[j,2]; c1:=c-q[j,3]; d1:=d-q[j,4];
142         if (a1<0) or (b1<0) or (c1<0) or (d1<0) then continue;
143         up(ans,s[i-1,v[a1,b1,c1,d1]]+int64(pre+j*p[i])*int64(f[i-1,v[a1,b1,c1,d1]]) mod mo);
144 //和与处理的转移类似
145       end;
146       a:=a-q[r[i],1]; b:=b-q[r[i],2]; c:=c-q[r[i],3]; d:=d-q[r[i],4];
147       up(pre,r[i]*p[i] mod mo);
148       if (a<0) or (b<0) or (c<0) or (d<0) then break;
149     end;
150     if now=k then up(ans,x mod mo);  //判断x
151     exit(ans);
152   end;
153 
154 function calc(x:int64):longint;  //乘积为0的转移要复杂,容易出错
155   var i,j,pre,ans,e:longint;
156   begin
157     get(x);
158     ans:=sum(p[m]) mod mo;  //先求1~m-1乘积为0的数和,我们可以用补集的思想,总和-不含0的数和
159     for i:=1 to m-1 do
160       up(ans,-g[i]);
161     pre:=0;
162     for i:=m downto 1 do
163     begin
164       if r[i]=0 then
165       begin
166         up(ans,int64(pre)*int64(x mod pp[i] mod mo+1) mod mo+sum(x mod pp[i]+1));
167 //当前为0,后面对答案的贡献可以直接算出来
168         break;
169       end;
170       if i<m then up(ans,int64(pre)*int64(p[i]) mod mo+sum(pp[i]));
171 //否则m位数且小于等于x的数当前位可以是0,计算贡献
172       if i=1 then e:=r[i] else e:=r[i]-1;
173       for j:=1 to e do
174         up(ans,sum(pp[i])-g[i-1]+int64(pre+j*p[i])*int64((p[i]-h[i-1]+mo) mod mo) mod mo);
175 //这里还是运用到补集的思想,和之前算乘积不为0类似
176       up(pre,r[i]*p[i] mod mo);
177     end;
178     exit(ans);
179   end;
180 
181 begin
182   readln(n);
183   p[1]:=1;
184   pp[1]:=1;
185   for i:=2 to 18 do
186   begin
187     p[i]:=p[i-1]*10 mod mo;  //位权
188     pp[i]:=pp[i-1]*10;
189   end;
190   pre;
191   work;
192   for i:=1 to n do
193   begin
194     readln(x,y,w);
195     if w=0 then writeln((calc(y)-calc(x-1)+mo) mod mo)
196     else writeln((count(y,w)-count(x-1,w)+mo) mod mo);
197   end;
198 end.
View Code
原文地址:https://www.cnblogs.com/phile/p/4472933.html