[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) ...
随机推荐
- Odd CSS syntax. [class^='icon-'], [class*=' icon-']
原文: https://stackoverflow.com/questions/20322740/odd-css-syntax-class-icon-class-icon I am going thr ...
- Qt-序列号生成器
一直想做一个序列号的生成器,今天终于有了开始了,目前只有读取硬件生成,还没有做生成序列号的规则. 目前实现的功能就是读取系统磁盘卷加上CPU ID 和自己的加密字符串,在利用MD5加密,转换成标准32 ...
- Spring 配置请求过滤器,编码格式设为UTF-8,避免中文乱码
<!-- 配置请求过滤器,编码格式设为UTF-8,避免中文乱码--> <filter> <filter-name>springUtf8Encoding</fi ...
- fizzbuzz Python很有意思的解法
写一个程序,打印数字1到100,3的倍数打印“Fizz”来替换这个数,5的倍数打印“Buzz”,对于既是3的倍数又是5的倍数的数字打印“FizzBuzz” 题目不难,解起来容易,用for循环做if,e ...
- 【text】 文本组件说明
text文本组件:在小程序里除了文本节点以外的其他节点都无法长按选中. 原型: <text selectable="[Boolean]" space="[ensp ...
- CodeForces 838B Diverging Directions 兼【20180808模拟测试】t3
描述 给你一个图,一共有 N 个点,2*N-2 条有向边. 边目录按两部分给出 1. 开始的 n-1 条边描述了一颗以 1 号点为根的生成树,即每个点都可以由 1 号点到达. 2. 接下来的 N-1 ...
- org.apache.spark.launcher.Main源码分析
public static void main(String[] argsArray) throws Exception { //org.apache.spark.launcher.Main chec ...
- python3对接聊天机器人API
详情见http://api.qingyunke.com/智能机器人API接口说明支持功能:天气.翻译.藏头诗.笑话.歌词.计算.域名信息/备案/收录查询.IP查询.手机号码归属.人工智能聊天接口地址: ...
- Bootstrap框架(图标)
Glyphicons 字体图标 所有可用的图标 包括250多个来自 Glyphicon Halflings 的字体图标.Glyphicons Halflings 一般是收费的,但是他们的作者允许 Bo ...
- react和vue的区别
1.数据改变的方式 react是通过setState来改变数据,然后重走组件的渲染过程.而vue是通过Object.defineProperty和watcher来显示响应式的数据,所以数据的改变是直接 ...