JZOJ 4467【GDOI2016模拟4.22】数字方阵

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:“我不想要你的礼物,我只想要醒过来,我还有很多事情要做呢!”

Data Constraint

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

Analysis

还是一道脑洞题。
题目描述中的图非常好,我们就围绕图来讲。
我们可以枚举第一列的数,然后暴力出2,3,4…..n列的数。这样就有50分了(暴力分真多)。

for(i,1,N)
{
    ans[i]++;
    ans[i+rev(i)]+=ans[i];
}

这里,第一列的数灰常多。所以TLE。
我们可以打表,找规律。
发现第二列的数少很多。
于是把第二列的数构造出来,再暴力,就能解决问题了。
问题是如何构造。
我们找一下问题的特点。
模拟一下加法。
a1 a2 a3 a4
+
a4 a3 a2 a1

a1+a4 a2+a3 a3+a2 a4+a1
令a1+a4=b1,a2+a3=b2.
和就变成:
b1 b2 b2 b1
和的取值范围是0<=bi<=18.
因为A,B<=10^10,所以b最多有5个。
所以复杂度就是19^5。
构造时顺便算一下和的可能性拆分(拆分为2个数的和)。
做一遍前缀和,[L,R]的答案=ans[R]-ans[L-1]。

Code

比较恶心。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
using namespace std;
typedef long long ll;
const ll N=ll(1e10);
const int M=2600000;
int m,num,a[6],
b1[19]={1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2,1};
struct node
{
    ll x,y;
}ans[M],b[M];
ll sum[M];
bool cmp(node a,node b){return a.x<b.x;}
ll rev(ll x)
{
    ll t=0;
    for(;x;x/=10) t=t*10+x%10;
    return t;
}
void dfs(int k,int n)
{
    if(k>(n+1)/2)
    {
        int x=(n+1)/2;
        ll t=0,y=1,z;
        fo(i,1,x)
        {
            t=t*10+a[i],z=b1[a[i]];
            if(n&1 && i==x) z=!(a[i]&1);
            else
            if(i==1 && a[i]<10) z--;
            y*=z;
        }
        if(n&1) x--;
        fd(i,x,1) t=t*10+a[i];
        if(t<=N) b[++m].x=t,b[m].y=y;
        return;
    }
    int x=0;
    if(k==1) x=1;
    fo(i,x,18)
    {
        if(n&1 && k==n/2+1 && i&1) continue;
        a[k]=i;
        dfs(k+1,n);
    }
    a[k]=0;
}
int serch(int l,int r,ll k)
{
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(ans[mid].x>k) r=mid;
        else l=mid+1;
    }
    return l-1;
}
int main()
{
    freopen("table.in","r",stdin);
    freopen("table.out","w",stdout);
    fo(i,1,10) dfs(1,i);
    sort(b+1,b+m+1,cmp);
    fo(i,1,m)
        if(b[i].x!=b[i-1].x) ans[++num]=b[i];
        else ans[num].y+=b[i].y;
    fo(i,1,num)
    {
        ll j=ans[i].x+rev(ans[i].x);
        ans[serch(1,num,j)].y+=ans[i].y;
    }
    fo(i,1,num) sum[i]=sum[i-1]+ans[i].y;
    int _;
    ll l,r;
    scanf("%d",&_);
    while(_--)
    {
        scanf("%lld %lld",&l,&r);
        int posl=serch(1,num,l),posr=serch(1,num,r);
        if(ans[posl].x==l) posl--;
        printf("%lld\n",sum[posr]-sum[posl]+r-l+1);
    }
    fclose(stdin);fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值