【GDOI2016模拟4.23】数字方阵

Description

有一个无限大的矩阵A,满足A[i,1]=i,A[i,j]=A[i,j-1]+rev(a[i,j-1])
求这个矩阵中,在区间a~b中间的数的数量。
多组询问。
Type<=10^5,a<=b<=10^10

Solution

一眼数论(蒟蒻)
然后发现不会打。
其实我们可以发现,对于每一行,都是递增存在的,并且出现2次以上的数不会特别多,也就(19^5+19^4+19^3+19^2+19^1)*2个那么多。
那么,我们可以预处理出所有在第二列出现过的数,以及他们的出现次数。
a1a2a3…aL+aLaL-1…a1=x1….xL
那么对称过来每一位的取值相乘就是次数。
注意a1不能为0。
打完这个表,我们可以再处理处每个数的实际出现次数。
从小到大拍一遍序,然后每一位sum[i+rev(i)]+=sum[i]
这样每一个询问二分求解就行了。
细节比较多,自己想(看代码)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 6400005
using namespace std;
typedef long long ll;
struct note{ll v,sum;}a[N];
bool cmp(note x,note y) {return x.v<y.v;}
int ty,len,tot,k,app[20];
ll b[6],sum[N],h[N+5],ha[N+5],x,y;
ll hash(ll x) {
    int t=x%N;
    while (h[t]&&h[t]!=x) t=t%N+1;
    return t;
}
void dfs(int x) {
    if (x>k) {
        ll v=0,cnt,l,x,sum=1;
        fo(i,1,k) {
            if (i==k&&len%2) x=!(b[i]%2);
            else x=app[b[i]];
            if (i==1&&!(i==k&&len%2)&&b[i]<10) x--;
            sum*=x;
        }
        if (!sum) return;
        fd(i,len,1) {
            if (len%2) l=2*k-i;else l=2*k-i+1;
            if (i>k) cnt=b[l];
            else cnt=b[i];
            v=v*10+cnt;
        }
        l=hash(v);
        if (!h[l]) a[++tot].v=v,a[tot].sum=sum,h[l]=v,ha[l]=tot;
        else a[ha[l]].sum+=sum;
        return;
    }
    int low;
    if (x==1) low=1;else low=0;
    fo(i,low,18) b[x]=i,dfs(x+1);
}
ll rev(ll x) {
    ll z=0;
    while (x) {z=z*10+x%10;x/=10;}
    return z;
}
int find(int l,int r,ll x) {
    int mid;
    while (l<r) {
        mid=(l+r)/2;
        if (a[mid].v>x) r=mid;else l=mid+1;
    }
    return l-1;
}
int main() {
    freopen("table.in","r",stdin);
    freopen("table.out","w",stdout);
    fo(i,0,9) fo(j,0,9) app[i+j]++;
    for(len=1;len<=10;len++) k=len/2+len%2,dfs(1);
    sort(a+1,a+tot+1,cmp);
    fo(i,1,tot) {
        ll k=a[i].v+rev(a[i].v);
        if (k<=a[tot].v) a[find(1,tot,k)].sum+=a[i].sum;
    }
    fo(i,1,tot) sum[i]=sum[i-1]+a[i].sum;
    for(scanf("%d",&ty);ty;ty--) {
        scanf("%lld%lld",&x,&y);
        int l=find(1,tot,x),r=find(1,tot,y);
        if (a[l].v==x) l--;
        printf("%lld\n",sum[r]-sum[l]+y-x+1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值