[SDOI2013]淘金 数位DP
做了好久。。。。
大致思路:
求出前k大的方格之和即为答案,
先考虑一维的情况,设f[i]为数位上各个数相乘为i的数的总数,也就是对于数i,有f[i]个数它们各个位相乘为i,
再拓展到二维,根据乘法原理(貌似是这个原理吧),方格(i , j)的金块数就是f[i] * f[j],
所以先数位DP求出f数组,然后贪心取前k大.
具体过程:
首先观察这道题的特殊性质,可以发现,由于是各个位上的数相乘得到贡献的目标,而各个位上的数只有:
1 2 3 4 5 6 7 8 9(如果有0的话金块就飞出去了,所以可以不算)
那么这几个数的质因子只有2,3,5,7,也就是说不为0的f[i]的i值只有2,3,5,7这4个质因子。
然后用计算器算一下就可以得知满足f[i]不为0的i不会很多,因为2^40就基本超数据范围了,
但是由于n过大,如果连f[i]为0的i也要参与运算就太浪费了,
所以考虑先预处理出可能符合要求的i.
以下提供两种方法:
1,dfs:
从0位开始搜索,
每次都递增(包括自己)的枚举下一位,
如果当前乘积超过n,return (超过n就出去了)
如果当前位==n的位数,统计答案并return(这样的话3会被003之类的统计到)
2,根据质因子只有2,3,5,7,暴力枚举。
4个for分别枚举每种质因子的指数,
退出条件类似于dfs。
然后就是DP了。
虽然说已经找到了所有可能状态,但是状态太大了,放不进数组,
怎么办呢?
离散化,每次查询的时候二分一下就好了(当然map也是可以的,不过我不会用23333333)
f[i][j][k]表示dp到第i位,乘积是第j小的那个状态,k=0表示没有超过边界,1表示超过边界。
注意这里的边界是指前i位对比n的前i位是否超过边界(i越大位数越高)
三层for分别枚举i,j,x(下一位是什么)。
每次用当前状态对下一状态做出贡献。
由于是从低位开始枚举的,因此x占的比重更大,
所以根据x来分情况讨论转移方程.
rank表示加上x这位的乘积是第几大
1,若x > s[i+1](s[i]表示n的第i位)
则不管之前怎么样,都超出边界了
所以f[i+1][rank][1] += f[i][j][1] + f[i][j][0];
2,若x < s[i+1]
则不管之前怎么样,都不会超
所以f[i+1][rank][0] += f[i][j][1] + f[i][j][0];
3,x = s[i+1]
那么下一状态超不超完全取决于当前状态。
所以
f[i+1][rank][0] += f[i][j][0];
f[i+1][rank][1] += f[i][j][1];
然后我们发现对一个数i产生贡献的数的长度可能有很多种,
所以DP完之后,枚举产生贡献的数的长度统计入size[i]
其中长度为len(n的长度)时,
对i造成贡献的数,必须没有超过边界,因此只能加上f[len][i][0].
而其他长度都可以加上f[j][i][0] + f[j][i][1]
这里的size数组实际上就是前面思路里面的f数组,
现在我们得到了size数组,所以下一步就只需要统计答案了。
假设i表示行,j表示列,
显然对于指定的第i行来说,
要依次获得最大,只需要将j指针从大到小依次指向贡献大的列即可(此处size代表了贡献,因为行的限制已经满足了,所以看一个方格的贡献只需要看列,也就是size[列]就可以了)
那么如果我们先对size数组进行排序,那么最优答案必定由前k大的数互相搭配得到。
因此我们定义k个行指针,分别指向自己那一行,同时与这个行指针绑在一起的是一个列指针,先指向贡献最大的那一列,
然后用优先队列按size[行指针] * size[列指针]为标准维护大根堆。
每次取出最大的那一个,取k次,每次取的时候弹出指针,统计贡献,并将列指针移向下一个列(大小仅次于or等于当前列的列),然后放回优先队列中。
最后即可得到答案。
注意此题需要LL,不开会wa。
- #include<bits/stdc++.h>
- using namespace std;
- #define R register int
- #define mod 1000000007
- #define LL long long
- //#define int LL
- #define AC 401000
- int len;
- int s[];
- LL n,k,tmp,tot,cnt,ans,t;//error!!!tmp是用来放n的,所以也要LL啊
- LL f[][AC][],num[AC],may[AC],size[AC];
- struct node{
- LL v;
- int x,y;
- };
- bool operator < (node a,node b)
- {
- return a.v < b.v;
- }
- //1230784859 39640
- priority_queue <node> q;
- /*观察到实质上是要找到前k大金块最多的方格,
- 然后利用一点组合的知识可以得到:
- 如果用f[i]表示个位数乘积是i的数的个数,
- 那么块(i,j)的方块数就是f[i] * f[j],
- 于是问题变成了求出所有f[i],并找到任意不重复f[i] * f[j]的最大值,
- (格子不够就不取了)
- 然后因为金块移动的方式是向个位数乘积移动,而观察这些可能的因数,
- 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9,
- 由这几个数相乘得到的数,它们的质因子只有2 , 3 , 5 , 7。
- 并且观察到2 ^ 40就已经达到了1e12,所以总的有金块的格子不会太多,
- 所以应该可以利用这个来计算?
- 每个数都用2 ^ a * 3 ^ b * 5 ^ c * 7 ^ d来表示(这样大概就存的下了?)
- 所以就是要对每个可能的数求凑它有多少种方案
- f[i][j][k][l]四维DP,分别表示各个质因数的次方
- 实际上f只要开大概f[40][29][20][15]就可以了
- 不过因为总状态不多,所以可以先找到所有状态?
- 然后就按照数位DP的套路来,一位代表到了哪一位,一位是有没有在边界上,
- 这里还加一位表示是哪个数。
- f[i][j][k]表示到了第i位,乘积是第j小的那个数(相当于离散化),k表示是否超过了n(也就是边界)
- */
- bool cmp(int a,int b)
- {
- return a > b;
- }
- void dfs(int x,int length,LL mul)//当前位,当前长度,当前乘积,因为要保证小于n,error!!!mul要开LL啊
- {//其实也不用在意这里搜到的状态是否严格满足大小关系,如果不满足下面反正统计不到的
- if(!mul || mul > n) return ;//所以直接从小到大枚举防止重复组合即可,貌似手算可以发现,满足条件的mul一定不会大于n?
- if(length == len)//为了防止过多无用搜索,只搜到这一位就行了
- {//事实上就是因为这里搜到的状态并不严格满足大小关系,所以这样搜才不会遗漏,比如03这里直接乘会变成0
- //然而貌似并不会,这就是从第0位开始的原因了吧
- num[++cnt]=mul;//所以搜到03的假等价条件为搜到13,虽然实际上n不一定大于这个,但是为了保证正确性
- return;//多搜一点也没关系
- }
- for(R i=x;i<=;i++)
- dfs(i,length+,mul * i);
- }//其实也可以按照原来的思路,直接枚举每个质因子的指数
- //其实从低位开始枚举也是有用的,因为这样是为了能搜到个位数
- void pre()
- {
- scanf("%lld%lld",&n,&k);
- tmp=n;
- while(tmp)
- {
- s[++len]=tmp % ;
- tmp /= ;
- }
- dfs(,,);
- sort(num+,num+cnt+);
- for(R i=;i<=cnt;i++)
- if(num[i] != num[i+]) num[++tot]=num[i];//去重
- }
- inline int half(LL x)//二分这个数的下标error!!!x也要LL啊
- {
- int l=,r=tot,mid;//是从去重后的开始二分。。。。
- while(l < r)
- {
- mid=(l + r) >> ;
- if(x == num[mid]) return mid;//error明明是和num[mid]比较。。。。
- if(x > num[mid]) l=mid + ;
- else r=mid;
- }
- return l;
- }
- void work()
- {
- f[][][]=;//原因类似dfs,1的话就可以任意搭配了
- for(R i=;i<len;i++)
- for(R j=;j<=tot;j++)
- {//因为如果各个位上出现了0,那金块就出去了,所以是不能出现任何0的,但这样就不太好一次统计完了,
- for(R x=;x<=;x++)
- {
- int rank=half(num[j] * x);//所以干脆统计到第i位就只统计第i位的,不考虑之前的方案,最后再重新计入答案
- if(x > s[i+]) f[i+][rank][] += f[i][j][] + f[i][j][];//如果是大于的话,由于是在高位,所以不管前面怎么样都是大于了
- else if(x < s[i+]) f[i+][rank][] += f[i][j][] + f[i][j][];//同理,此处不管前面怎么样都是小于
- else//如果是等于的话大小关系就只受前面影响了
- {
- f[i+][rank][] += f[i][j][];
- f[i+][rank][] += f[i][j][];
- }
- }
- }
- for(R i=;i<=tot;i++)//枚举数字
- {
- for(R j=;j<len;j++)//枚举长度,长度没那么多的都可以超过n(在有的位置上),
- size[i]+=f[j][i][] + f[j][i][];
- size[i]+=f[len][i][];
- }
- //for(R i=1;i<=tot;i++) printf("%lld %lld\n",num[i],size[i]);
- sort(size + ,size + tot + ,cmp);
- // for(R i=1;i<=tot;i++) printf("%lld %lld\n",num[i],size[i]);
- }
- /*用k个指针(其实不需要这么多?)
- 一开始都指向自己,指针i表示横坐标为i,指向j表示纵坐标为j,
- 因为同一个i,肯定是从大的纵坐标开始取,所以一开始i指向自己,然后如果取了这个,
- 那就指向下一个纵坐标*/
- void getans()
- {
- node x;int en=min(k,tot);
- for(R i=;i<=en;i++)
- q.push((node) {size[i] * size[],i,});//指针先指向1,,,这里要改完啊
- for(R i=;i<=k;i++)
- {
- if(q.empty()) break;
- x=q.top();
- q.pop();
- ans=(ans + x.v) % mod;
- x.y++;//移动指针
- x.v=size[x.x] * size[x.y];//更新金块数量
- q.push(x);//再放回去
- }
- printf("%lld\n",ans);
- // printf("time used %lf\n",(double)clock()/CLOCKS_PER_SEC);
- }
- int main()
- {
- // freopen("in.in","r",stdin);
- pre();
- work();
- getans();
- // fclose(stdin);
- return ;
- }
由于本人有打注释的习惯,所以看上去可能有点乱,以下为无注释版本:
- #include<bits/stdc++.h>
- using namespace std;
- #define R register int
- #define mod 1000000007
- #define LL long long
- #define AC 401000
- int len;
- int s[];
- LL n,k,tmp,tot,cnt,ans,t;
- LL f[][AC][],num[AC],may[AC],size[AC];
- struct node{
- LL v;
- int x,y;
- };
- bool operator < (node a,node b)
- {
- return a.v < b.v;
- }
- priority_queue <node> q;
- bool cmp(int a,int b)
- {
- return a > b;
- }
- void dfs(int x,int length,LL mul)
- {
- if(!mul || mul > n) return ;
- if(length == len)
- {
- num[++cnt]=mul;
- return;
- }
- for(R i=x;i<=;i++)
- dfs(i,length+,mul * i);
- }
- void pre()
- {
- scanf("%lld%lld",&n,&k);
- tmp=n;
- while(tmp)
- {
- s[++len]=tmp % ;
- tmp /= ;
- }
- dfs(,,);
- sort(num+,num+cnt+);
- for(R i=;i<=cnt;i++)
- if(num[i] != num[i+]) num[++tot]=num[i];
- }
- inline int half(LL x)
- {
- int l=,r=tot,mid;
- while(l < r)
- {
- mid=(l + r) >> ;
- if(x == num[mid]) return mid;
- if(x > num[mid]) l=mid + ;
- else r=mid;
- }
- return l;
- }
- void work()
- {
- f[][][]=;
- for(R i=;i<len;i++)
- for(R j=;j<=tot;j++)
- {
- for(R x=;x<=;x++)
- {
- int rank=half(num[j] * x);
- if(x > s[i+]) f[i+][rank][] += f[i][j][] + f[i][j][];
- else if(x < s[i+]) f[i+][rank][] += f[i][j][] + f[i][j][];
- else
- {
- f[i+][rank][] += f[i][j][];
- f[i+][rank][] += f[i][j][];
- }
- }
- }
- for(R i=;i<=tot;i++)
- {
- for(R j=;j<len;j++)
- size[i]+=f[j][i][] + f[j][i][];
- size[i]+=f[len][i][];
- }
- sort(size + ,size + tot + ,cmp);
- }
- void getans()
- {
- node x;int en=min(k,tot);
- for(R i=;i<=en;i++)
- q.push((node) {size[i] * size[],i,});
- for(R i=;i<=k;i++)
- {
- if(q.empty()) break;
- x=q.top();
- q.pop();
- ans=(ans + x.v) % mod;
- x.y++;
- x.v=size[x.x] * size[x.y];
- q.push(x);
- }
- printf("%lld\n",ans);
- }
- int main()
- {
- pre();
- work();
- getans();
- return ;
- }
[SDOI2013]淘金 数位DP的更多相关文章
- BZOJ 3131 [SDOI2013]淘金 - 数位DP
传送门 Solution 这道数位$DP$看的我很懵逼啊... 首先我们肯定要先预处理出 $12$位乘起来的所有的可能情况, 记录入数组 $b$, 发现个数并不多, 仅$1e4$不到. 然后我们考虑算 ...
- bozoj3131: [Sdoi2013]淘金 数位dp
链接 https://www.lydsy.com/JudgeOnline/problem.php?id=3131 思路 1. 函数值的素因子只有2.3.5.7 由他们组成的状态不多,爆搜的时候即使搜不 ...
- [Bzoj3131][Sdoi2013]淘金(数位dp)(优先队列)
3131: [Sdoi2013]淘金 Time Limit: 30 Sec Memory Limit: 256 MBSubmit: 847 Solved: 423[Submit][Status][ ...
- bzoj 3131 [Sdoi2013]淘金(数位DP+优先队列)
Description 小Z在玩一个叫做<淘金者>的游戏.游戏的世界是一个二维坐标.X轴.Y轴坐标范围均为1..N.初始的时候,所有的整数坐标点上均有一块金子,共N*N块. 一阵风吹 ...
- bzoj 3131 [Sdoi2013]淘金(数位dp)
题目描述 小Z在玩一个叫做<淘金者>的游戏.游戏的世界是一个二维坐标.X轴.Y轴坐标范围均为1..N.初始的时候,所有的整数坐标点上均有一块金子,共N*N块. 一阵风吹过,金子的位置发生了 ...
- [您有新的未分配科技点]数位DP:从板子到基础(例题 bzoj1026 windy数 bzoj3131 淘金)
只会统计数位个数或者某种”符合简单规律”的数并不够……我们需要更多的套路和应用 数位dp中常用的思想是“分类讨论”思想.下面我们就看一道典型的分类讨论例题 1026: [SCOI2009]windy数 ...
- 数位DP学习笔记
数位DP学习笔记 什么是数位DP? 数位DP比较经典的题目是在数字Li和Ri之间求有多少个满足X性质的数,显然对于所有的题目都可以这样得到一些暴力的分数 我们称之为朴素算法: for(int i=l_ ...
- Bzoj 3131 [Sdoi2013]淘金 题解
3131: [Sdoi2013]淘金 Time Limit: 30 Sec Memory Limit: 256 MBSubmit: 733 Solved: 363[Submit][Status][ ...
- 题解-SDOI2013 淘金
题面 SDOI2013 淘金 有一个 \(X\).\(Y\) 轴坐标范围为 \(1\sim n\) 的范围的方阵,每个点上有块黄金.一阵风来 \((x,y)\) 上的黄金到了 \((f(x),f(y) ...
随机推荐
- SpringBoot-05:SpringBoot初运行以及tomcat端口号的修改
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 上篇博客讲了,如何创建SpringBoot工程,所以,我本篇博客讲述,如何跑起来自己的第一个案例 1.准备一个 ...
- Spring Boot 2.x Redis多数据源配置(jedis,lettuce)
Spring Boot 2.x Redis多数据源配置(jedis,lettuce) 96 不敢预言的预言家 0.1 2018.11.13 14:22* 字数 65 阅读 727评论 0喜欢 2 多数 ...
- Appium1.8及以上命令行启动
安装命令行启动版本的Appium,appium-doctor需要独立下载了,用 npm的话需要FQ才好使,所有安装了cnpm代替npm, cnpm是从淘宝的国内镜像下载 npm config rm p ...
- android自动化のadb常用命令(不定期更新)
1. adb devices 执行结果是adb为每一个设备输出以下状态信息:序列号(serialNumber) — 由adb创建的使用控制台端口号的用于唯一标识一个模拟器或手机设备的字符串,格式是 & ...
- 第一阶段·Linux运维基础 第3章·文件属性、正则表达式、文件权限
01-文件属性内容介绍 02- inodeyublock讲解 03-访问oldboyfile的寻宝过程 04-inode与block小结 05-磁盘空间不足案例详解 06-Linux文件类型及拓展名 ...
- Java 语法基础
一 关键字 关键字: 其实就是某种语言赋予了特殊含义的单词 保留字: 其实就是还没有赋予特殊含义 但是准备日后要使用过的单词 二 标示符 标示符: 其实就是在程序中自定义的名词 比如类名, 变量名, ...
- redis 在java中的使用
1.首先下载jar包放到你的工程中 2.练习 package com.jianyuan.redisTest; import java.util.Iterator;import java.util.Li ...
- bson文件的切分
描述 最近遇到问题需要将较大的bson文件(MongoDB导出的二进制json文件)按文档(记录)进行切分,网上这方面的资料实在太少,弄了一天多终于达到了基本要求(还不知道有没有BUG) 代码 pac ...
- 深度学习笔记 (一) 卷积神经网络基础 (Foundation of Convolutional Neural Networks)
一.卷积 卷积神经网络(Convolutional Neural Networks)是一种在空间上共享参数的神经网络.使用数层卷积,而不是数层的矩阵相乘.在图像的处理过程中,每一张图片都可以看成一张“ ...
- 以太坊开发(二)使用Ganache CLI在私有链上搭建智能合约
以太坊开发(二)使用Ganache CLI在私有链上搭建智能合约 在上一篇文章中,我们使用Truffle自带的客户端Truffle Develop,在私有链上搭建并运行了官方提供的WebPack智能合 ...