【GDOI2016模拟4.22】数字方阵

37 篇文章 0 订阅

Description

Anica 做了一个很奇怪的梦:她梦见了一个无限大的平板,平板上填着无限行和无限列的整数。有趣的是,每个整数在那神奇的平板上只出现有限的次数。
机智的Anica很快便发现了这其中数字的规律,每一行第一列的数字表示当前的行号,其它非第一列的数字,为该位置左边一列的数字加上其数字的翻转数字之和。为了方便描述,我们定义A[i,j]表示平板上第i行第j列的数字:
1. A[i,1] = i,
2. A[i,j] = A[i,j-1] + Rev( A[i,j-1] ), 其中Rev(x)表示x在10进制下的翻转,例如Rev(213) = 312, Rev(406800) = 008604 = 8604 。
然而,Anica对这堆数字不太感兴趣。她看了一下平板的背面,并发现了一盏神灯,同时神灯也发现了Anica,这时一个精灵冒了出来:
精灵:“Anica,如果你能答对我Q个问题,你将获得一份神秘的礼物,否则你将无法无法醒来,哈哈哈哈~~”
精灵:“每个问题将给你两个整数A和B,请问区间[A,B]的数字总共在平板上出现了多少次?”
Anica:“我不想要你的礼物,我只想要醒过来,我还有很多事情要做呢!”

Input

第一行给出一个整数Q (1 <= Q <=10^5),表示问题的数量。
接下来Q行,每行两个整数A,B (1 <= A,B <= 10^10),表示每个询问的区间[A,B]。

Output

输出Q行,第i行一个整数,表示第i个询问的答案。

Sample Input

样例输入1:
2
1 10
5 8
样例输入2:
3
17 144
121 121
89 98
样例输入3:
1
1 1000000000

Sample Output

样例输出1:
18
8
样例输出2:
265
25
10
样例输出3:
1863025563

Data Constraint

50%的分数保证1 <= A,B <= 10^6。

Solution

对于50%:发现数据比较小,暴力。直接暴力第一列的数,每次f[x+rev(x)]+=f[x]。对f求前缀和,每次询问就是f[r]-f[l-1]。比较简单。
对于100%:
同样是暴力,但是要特别一点。
可以发现,总共的数的数量不会超过 195 ,等会证明。那么,如何暴力这些数呢?
如果
x长为L=a1 a2 a3.. aL
rev(x)=aL aL-1 .. a1
x+rev(x)=a1+aL a2+aL-1 a3+aL-2 aL+a1
得到一个对称的数。L最大为10,那么一半就是5。分别为0~18中一个共19个,所以大约有 195 个数。
爆搜这些数,算出它在第二列时有多少种可能从第一列转移过来,存到f里面。接着同50分一样,最后二分一下就行了。
注意:存数用hash,表开大一点。最好所有数组与变量开longlong。

code

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <iostream>
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define ll long long
#define mo 6412345
using namespace std;
struct note
{
    ll x,y;
};
note a[mo];
ll b[6],hsh[mo+5],has[mo+5],len,k=0,c[19],tot=0;
ll rev(ll x)
{
    ll jy=0;
    for(;x;x/=10) jy=jy*10+x%10;
    return jy;
}
ll hash(ll x)
{
    ll y;
    y=x%mo+1;
    while (hsh[y]!=0 && hsh[y]!=x) y=(y+1)%mo;
    return y;
}
void dg(int j)
{
    if (j>len)
    {
        ll x=1,y=0;
        fo(i,1,len) 
        {
            ll jy=c[b[i]];  
            if (i==1 && b[i]<10) jy--;
            if (i==len && k%2==1) 
            {
                if(b[i]%2==1) jy=0;
                else jy=1;
            }
            x*=jy;
            if (i!=len) y=y*10+b[i];
        }
        if (x==0) return;
        if (k%2==0) y=y*10+b[len];
        for(int i=len;i>=1;i--) y=y*10+b[i];
        ll jy=hash(y);
        if (hsh[jy]==0) a[++tot].x=y,a[tot].y=x,hsh[jy]=y,has[jy]=tot;
        else a[has[jy]].y+=x;
        return; 
    }
    ll kk;kk=j==1?1:0;
    fo(i,kk,18)
    {
        b[j]=i;dg(j+1);
    }
}
bool cmp(note x,note y){return x.x<y.x;}
ll find(ll l,ll r,ll k)
{
    ll mid;
    for(;l+1<r;)
    {
        mid=(l+r)/2;
        if (a[mid].x<=k) l=mid;else r=mid;
    }   
    if (a[r].x<=k) l=r;return l;
}
int main()
{
    freopen("table.in","r",stdin);freopen("table.out","w",stdout);
    fo(i,0,9) 
    fo(j,0,9) c[i+j]++;
    for(len=1;len<=5;len++)
    {
        k++;dg(1);k++;dg(1);
    }
    sort(a+1,a+1+tot,cmp);
    fo(i,1,tot) 
    {
        ll x=a[i].x;x=x+rev(x);
        if (x<=a[tot].x) a[find(0,tot,x)].y+=a[i].y;
    }
    fo(i,1,tot) a[i].y+=a[i-1].y;
    int ak;scanf("%d",&ak);
    while (ak-->0)
    {
        ll l,r;scanf("%lld%lld",&l,&r);
        ll x=find(0,tot,r);ll y=find(0,tot,l);
        if (a[y].x==l) y--;
        printf("%lld\n",a[x].y-a[y].y+r-l+1);
    }
    fclose(stdin);fclose(stdout);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值