BC #15 / hdu 5085 Counting problem · 分块+hash / 数位dp

题解

版本1:分块+hash
向大佬学习学习· 代码学习来源

分块部分:
将a分为前5位(记作头部ah),和后4位(记作尾部at),b同样划分,
a = a h ∗ 10000 + a t a=ah*10000+at a=ah10000+at,(a要先减1)
b = b h ∗ 10000 + b t b=bh*10000+bt b=bh10000+bt

hash部分:

如果 a t < b t at<bt at<bt

  1. 先暴力将尾部是 0~at 插入哈希表中,然后用 ah 作为头部去匹配哈希表,查询 ah0000 ~ a (ahat) 的答案
  2. 再暴力将尾部是 at+1~bt 插入哈希表中(现在哈希表中是 0~bt 的范围了),然后用 bh 作为头部去匹配哈希表,查询 bh0000 ~ b (bhbt) 的答案
  3. 最后将剩余的部分 bt+1~9999 插入(现在表中是 0~ 9999的范围了),枚举 ah ~ (bh-1) 所有作为头部的可能的答案,得到ah0000 ~ (bh-1)9999

整理一下就是 ans = 第二步的统计结果 + 第三步 - 第一步
在这里插入图片描述

如果 a t > b t at>bt at>bt 等号放哪儿都一样

  1. 先暴力将尾部是 0~bt 插入哈希表中,然后用 bh 作为头部去匹配哈希表,查询 bh0000 ~ b (bhbt) 的答案
  2. 再暴力将尾部是 bt+1~at 插入哈希表中(现在哈希表中是 0~at 的范围了),然后用 ah 作为头部去匹配哈希表,查询 ah0000 ~ a 的答案
  3. 最后将剩余的部分 at+1~9999 插入(现在表中是 0~9999的范围了),枚举 ah ~ (bh-1) 所有作为头部的可能的答案,得到ah0000 ~ (bh-1)
    在这里插入图片描述

版本2:数位dp 抱歉,这个先等等,我还没理解透


在这里插入图片描述


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
const int M=(1<<20)-1;
/*
	这里帖一下上面链接的博客主的原话: 这真的是以现在的我的能力注意不到的地方呢
	
	还有一些比较巧妙的地方就是:
	1.用二进制来优化hash链表,用每一个数二进制下的后面20位来当第一维的下标,用每一个数的值来当第二位的下标,优化空间,也使得数组开的下
	2.对于hash的操作,预先开设一块固定内存,避免在堆上申请内存和释放,加快速度
	3.对于visit的数组的重用,每T组数据,某个值存在就将他标为visit[x]=T
	4.就是莫队分块,分成头尾两部分--因为f(x)的可加性
	
*/
struct node{
    ll key,cnt;
    node* nxt;
}* g[M+1];//g[i]是二进制最后20位为i的链表的头指针
node* cur;
node memory[N];//预先定义一块内存空间 hash的操作都在此执行
ll visit[M+1];//第T组数据下 二进制最后20位为i的链表内有没有元素
ll T=0,k,s;
ll dt[10][20];//各种数字的各种次幂

void ins(ll x){
    node* p;
    if(x>s)return ;//超过计算界限 退出
    ll u=x%M;//x二进制最后20位
    if(visit[u]<T){//在第T组数据下 二进制最后20位为u的链表内没有元素就加入
        //类似初始化了
        visit[u]=T;
        g[u]=NULL;
    }

    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;
    for (p = g[u]; p ; p=p->nxt) {
        if(p->key==x)return p->cnt;
    }
    return 0;
}

void init(){
    //预处理各种数的各种次
    for (int i = 0; i <= 9; ++i) {
        dt[i][0]=1;
        for (int j = 1; j <= 15; ++j) {
            dt[i][j]=dt[i][j-1]*i;
        }
    }
}

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

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

int main(){
    ios::sync_with_stdio(0);

    ll a,b;
    int sqrt=10000;
    ll ah,bh,at,bt;
    ll reta,retb;
    init();
    while(cin>>a>>b>>k>>s){
        init_hash();
        reta=retb=0;
        ah=(a-1)/sqrt;bh=b/sqrt;
        at=(a-1)%sqrt;bt=b%sqrt;//预先处理处a和b的头部与尾部

        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 <sqrt ; ++i) //将剩余的sqrt插入表中
                ins(cal(i));
            
        }else{
            for (int i = 0; i <= bt; ++i) 
                ins(cal(i));//将尾部是0~bt的值插入表中
            retb=ask(cal(bh));//得到头部是bh 尾部是0~bt的答案 即 bh*10000<= 数 <= b (bh*bt) 中的答案
            
            for (int i = bt + 1; i <=at; ++i) 
                ins(cal(i));//再将尾部是bt+1~at的值插入表中
            reta=ask(cal(ah));//得到 头部是ah 尾部是0~at的答案 即 ah*10000<= 数 <=a (ah*at)
            
            for (int i = at + 1; i <sqrt; ++i) 
                ins(cal(i));
        }
        
        for (int i = ah; i < bh; ++i) 
            retb+=ask(cal(i));
            //根据头部去匹配现在表中的值 求得 头部是ah<= <bh 尾部是0~sqrt-1 即 a-1<= 数 <b*10000 的答案
        cout <<retb-reta << endl;
    }
    return 0;
}

造诣不够,看不懂,先占个坑,让我多学会儿再来解解数位dp
代码学习来源

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,k;
ll a,b,s;
ll pos[20];
ll f[20];//全排列
ll p[20][20];//各种数字的各种次方

//len 表示目前还有几位没有确定下来
//sum 表示要组合成的数
//top 目前在讨论的数字 先讨论9 再讨论8...直到0
ll dfs(int len,ll sum,int top){
    if(len*p[top][k]<sum) return 0;//如果全部都是最高位加起来都没有总和大 直接返回0
    if(sum<0)return 0;
    if(top==0){
        if(sum) return 0;
        pos[0]=len;
        ll res=f[n];
        for (int i = 0; i <=9; ++i) {
            res/=f[pos[i]];
            //一个全排列就是符合要求的数的个数 因为首位是什么已经在solve()中的for里面决定好了
            //所以后面首位是0页没关系 只要找到符合条件的数的个数 计算出他们可以组合出集中不同的个数就好了
        }
        return res;
    }
    ll res=0;
    for (int i = 0; i <= len; ++i) {
        pos[top]=i;//目前讨论的数字所在的位置
        res+=dfs(len-i,sum-i*p[top][k],top-1);
    }
    return res;
}

//在dfs前先做一些处理 比如剪枝 以及记录dfs开始前有几位数还没有被确定下来
ll sub_solve(int len,ll sum){
    if(len==0)return sum==0;
    n=len;
    return dfs(len,sum,9);
}

int len,bit[20];
ll solve(ll x){//求[1,x]内符合条件的数字的个数
    len=0;
    while(x){
        bit[++len]=x%10;
        x/=10;
    }

    ll res=0,sum=s;
    for (int i = len; i ; --i) {
        for (int j = 0; j <bit[i]; ++j) {
            res+=sub_solve(i-1,sum-p[j][k]);
            //目前讨论的数没有取到最大值(j没有等于bit[i]) 则后面的数可以随便取
        }
        sum-=p[bit[i]][k];//取到了最大值
    }
    res+=(sum==0);
    return res;
}

int main(){
    ios::sync_with_stdio(0);
    //init
    f[0]=1;//全排列
    for (int i = 1; i <= 20; ++i) {
        f[i]=i*f[i-1];
    }
    for (int i = 1; i < 10; ++i) {
        p[i][0]=1;//i的各种次方
        for (int j = 1; j < 16; ++j) {
            p[i][j]=p[i][j-1]*i;
        }
    }

    while(cin>>a>>b>>k>>s){
        cout<<solve(b)-solve(a-1)<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值