hdu 5085 Counting problem (分块+二进制优化下hash链表)

Counting problem

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 630    Accepted Submission(s): 152


Problem Description
Supose that n has L digits, we used d0,d1,d2,,dL1 to indicate every digit of n from least significant digit to most significant one.
f(n,k)=i=0L1dki , for example f(305,2)=52+32+02=34
We are curious about that for each x ranged from a to b(
ab ), how many integers make S=f(x,k) (here S is a constant) true.
 

Input
Multi test cases (about 100), every case gives four integers a, b, k, S in a single line.
Please process to the end of file.

[Technical Specification]
1ab999999999
1k15
1S1016
 

Output
For each case,output a number in a single line indicates how many x ranged from a to b makes S=f(x,k) true.
 

Sample Input
  
  
1 1 1 1 1 10 2 1
 

Sample Output
  
  
1 2
 

Source

 


题意:

就是问你[a,b]区间内有多少x(a1a2a3...an),使得f(x)=a1^k+a2^k+a3^k+..+an^k=s


解析:

可以看大神博客上的解析,用了莫队分块的思想

https://www.cnblogs.com/bin-gege/p/5696092.html


还有一些比较巧妙的地方就是:

1.用二进制来优化hash链表,用每一个数二进制下的后面20位来当第一维的下标,用每一个数的值来当第二位的下标,优化空间,也使得数组开的下

2.对于hash的操作,预先开设一块固定内存,避免在堆上申请内存和释放,加快速度

3.对于visit的数组的重用,每T组数据,某个值存在就将他标为visit[x]=T

4.就是莫队分块,分成头尾两部分--因为f(x)的可加性


#include<cstdio>
#include<cstring>
typedef long long ll;

const int M = (1<<20)-1;
const int N = 1e4+10;

typedef struct node
{
    ll key;
    ll cnt;  //与key相同的数的个数
    node *nxt;
}node;
node *g[M+1],*cur;   //g[i]是二进制最后20位为i的链表(一组key)的头指针
node memory[N];  //预先定义的一块内存空间,HASH的操作都在此执行
ll visit[M+1];    //在第T组数据下,二进制最后20位为i的链表内有没有元素
ll T=0,k,s;
ll dt[16][10];

void ins(ll x)
{
    node *p;
    if(x>s) return;  //超过界限退出
    ll u=x&M;   //x二进制的最后20位
    if(visit[u]<T) visit[u]=T,g[u]=NULL;//在第T组数据下,二进制最后20位为u的链表内没有元素,就加入
    for(p=g[u];p;p=p->nxt)  //在二进制最后20位为u的链表中找值为x的节点
    {
        if(p->key==x)
        {
            p->cnt++;
            return;
        }
    }
    p=cur++;p->key=x;  //在在二进制最后20位为u的链表头部新建一个key为x的节点
    p->cnt=1;p->nxt=g[u];
    g[u]=p;

}

ll ask(ll x)
{
    node *p;
    if(x>s) return 0;
    x=s-x;
    ll u=x&M;
    if(visit[u]<T) return 0;  //在第T组数据下,二进制最后20位为u的链表内没有元素
    for(p=g[u];p;p=p->nxt)
    {
        if(p->key==x)
        {
            return p->cnt;
        }
    }
    return 0;
}


void init()   //预处理num(0...9)^k(1...15)的值
{
    for(int i=0;i<=9;i++)
    {
        for(int j=1;j<=15;j++)
        {
            if(j==1)
            {
                dt[j][i]=i;
            }
            else
            {
                dt[j][i]=dt[j-1][i]*i;
            }
        }
    }
}

ll cal(ll x)  //计算f(x,k)的值
{
    ll res=0;
    while(x)
    {
        res+=dt[k][x%10];
        x=x/10;
    }
    return res;
}

void init_Hash()
{
    T++;
    cur=memory;   //将cur重新指向该块内存的起始
}

int main()
{
    ll a,b;
    int sqr=10000;
    ll ah,bh,at,bt;
    ll reta,retb;
    init();
    while(scanf("%lld%lld%lld%lld",&a,&b,&k,&s)!=EOF)
    {
        //printf("****\n");
        init_Hash();
        reta=retb=0;
        ah=(a-1)/sqr;bh=b/sqr;  //处理出a,b的头部和尾部,因为后面要减a且题目是闭区间,所以a-1
        at=(a-1)%sqr;bt=b%sqr;
        if(at<bt)
        {
            for(int i=0;i<=at;i++) ins(cal(i));    //先将尾部是0-at的值插入表中
            reta=ask(cal(ah));   //根据现在表中的值去跟头部匹配,求得数(头部是ah,尾部是0-at,即ah*10000<=数<=a-1)中的答案
            for(int i=at+1;i<=bt;i++) ins(cal(i));    //将尾部是at+1-bt的值插入表中
            retb=ask(cal(bh));   //根据现在表中的值去跟头部匹配,求得数(头部是bh,尾部是0-bt,即bh*10000<=数<=b)中的答案
            for(int i=bt+1;i<sqr;i++) ins(cal(i));  //将剩余sqr中的值插入到表中
        }
        else
        {
            for(int i=0;i<=bt;i++) ins(cal(i));
            retb=ask(cal(bh));
            for(int i=bt+1;i<=at;i++) ins(cal(i));
            reta=ask(cal(ah));
            for(int i=at+1;i<sqr;i++) ins(cal(i));
        }
        for(int i=ah;i<bh;i++) retb+=ask(cal(i));   //根据头部去匹配现在表中的值,求得数(头部是ah<= <bh,尾部是0-sqr-1,即a-1<=数<bh*10000)中的答案

        printf("%lld\n",retb-reta);
    }
    return 0;

}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值