bzoj2757【scoi2012】Blinker的仰慕者

题目描述

Blinker 有非常多的仰慕者,他给每个仰慕者一个正整数编号。而且这些编号还隐藏着特殊的意义,即编号的各位数字之积表示这名仰慕者对Blinker的重要度。 现在Blinker想知道编号介于某两个值A,B之间,且重要度为某个定值K的仰慕者编号和。 


输入格式

输入的第一行是一个整数N,表示Blinker想知道的信息个数。 
接下来的N行,每行有三个数,A,B,K。表示 Blinker想知道编号介于A和B之间的,
重要度为K的仰慕者的编号和。           


输出格式


输出N行,每行输出介于A和B之间,重要度为 K的仰慕者编号和。结果可能很大,
模上20120427。 


提示

【数据范围】 
对于20%的数据,保证:  2<=A<=B<=1000000000,1<=N<=30 
对于50%的数据,保证:2<=A<=B<=1000000000000000000,1<=N<=30 
对于100%的数据,保证:  2<=A<=B<=1000000000000000000,1<=N<=5000


 

  • 题解:

    • 由于因子只有2 3 5 7 , 状态大概有3e4+,不超过4e4;
    • 用HASH_TABLE存下来;
    • 多组询问,先预处理$i$位的答案;
    • 对每组询问$dfs$统计答案,如果没有前导零并且没有抵达上界则直接返回,否则进一步$dfs$
    • 代码不太好些,注释里有关于变量的说明;
    • 注意$K=0$和$K \neq 0$最好分开统计;
    • $n$为位数上界,时间复杂度:$O(T*10n +n*4e4*10)$
 1 #include<bits/stdc++.h>
 2 #define ll long long 
 3 using namespace std;
 4 const int N=20,M=4e4,sz=1234561,mod=20120427;
 5 ll A,B,K;
 6 int cnt,a[N],pw[N];
 7 int o,hd[sz],nt[sz];ll v[sz];
 8 void upd(int&x,int y){x+=y;if(x>=mod)x-=mod;}
 9 void add(ll x){
10     int u=x%sz;
11     for(int i=hd[u];i;i=nt[i])if(v[i]==x)return;
12     nt[++o]=hd[u],hd[u]=o,v[o]=x;
13 } 
14 int find(ll x){
15     int u=x%sz;
16     for(int i=hd[u];i;i=nt[i])if(v[i]==x)return i;
17     return 0;
18 }//HASH表 
19 void dfs(int x,int y,ll z){
20     if(x==18)add(z);
21     else{
22         if(!y)return;
23         for(int i=x;i<=18;++i){
24             dfs(i,y-1,z);
25             z*=y;
26         }
27     }
28 }//dfs方案 
29 namespace Solve{
30     int f[N][M],g[N][M];//f[位数][乘积] 
31     void init(){
32         f[0][1]=1;
33         for(int i=0;i<18;++i)
34         for(int j=1;j<=o;++j)
35         if(f[i][j]){
36             for(int l=0;l<=9;++l){
37                 int y=find(v[j]*l);
38                 upd(f[i+1][y],f[i][j]);
39                 upd(g[i+1][y],(g[i][j]*10+f[i][j]*l)%mod);
40             }
41         }
42     }//预处理i位无限制的答案 
43     int cal1(int i,int j,int k,ll x,ll y){//已经枚举好的位数,前导零,上界,前i位的值,当前K 
44         if(i==cnt)return y==1?x:0; 
45         if(!k&&!j)return (1ll*x*pw[cnt-i]%mod*f[cnt-i][find(y)]%mod+g[cnt-i][find(y)])%mod; 
46         int re=0,l=j?0:1,r=k?a[i+1]:9;
47         for(int i1=l,t;i1<=r;++i1)if(!i1||y%i1==0){
48             ll y1=!i1?y:y/i1;
49             upd(re, t=cal1(i+1,j&&!i1,k&&i1==a[i+1],(x*10+i1)%mod,y1));
50         }
51         return re;
52     }
53     int cal0(int i,int j,int k,ll x,ll y){//前四个同cal1,y为前i为是否出现0 
54         if(i==cnt)return !y?x:0;
55         if(!k&&!j){
56             if(!y)return (1ll*x*pw[cnt-i]%mod*pw[cnt-i]%mod+1ll*(pw[cnt-i]-1)*pw[cnt-i]/2%mod)%mod;//后面cnt-i任意; 
57             else return 1ll*x*pw[cnt-i]%mod*f[cnt-i][o]%mod+g[cnt-i][o];//后面cnt-i为乘积为0; 
58         }
59         int re=0,l=0,r=k?a[i+1]:9;
60         for(int i1=l;i1<=r;++i1){
61             int y1=j&&!i1?1:y*i1;
62             upd(re, cal0(i+1,j&&!i1,k&&i1==a[i+1],(x*10+i1)%mod,y1));
63         }
64         return re;
65     } 
66     //注意理解一下两个方面:
67     //是如何统计前i位达到上界,i+1位不是上界的答案的;(状态k)
68     //是如何统计位数小于上界位数cnt的答案的; (状态j)
69     //上界是如何被统计的(line44)  
70 }
71 int main(){
72     #ifndef ONLINE_JUDGE
73     freopen("T2.in","r",stdin);
74     freopen("T2.out","w",stdout);
75     #endif
76     dfs(0,9,1);add(0);
77     Solve::init();
78     for(int i=pw[0]=1;i<=18;++i)pw[i]=pw[i-1]*10%mod;
79     int T;scanf("%d",&T);
80     while(T--){
81         int ans=0;
82         scanf("%lld%lld%lld",&A,&B,&K);A--;
83         cnt=0;while(A)a[++cnt]=A%10,A/=10;
84         for(int i=1;i<=cnt>>1;++i)swap(a[i],a[cnt-i+1]);
85         ans-= K?Solve::cal1(0,1,1,0,K) : Solve::cal0(0,1,1,0,1);
86         cnt=0;while(B)a[++cnt]=B%10,B/=10;
87         for(int i=1;i<=cnt>>1;++i)swap(a[i],a[cnt-i+1]);
88         ans+= K?Solve::cal1(0,1,1,0,K) : Solve::cal0(0,1,1,0,1);
89         printf("%d\n",(ans%mod+mod)%mod);
90     }
91     return 0;
92 }
View Code

转载于:https://www.cnblogs.com/Paul-Guderian/p/10410499.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值