[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) ...
随机推荐
- cf#516A. Make a triangle!(三角形)
http://codeforces.com/contest/1064/problem/A 题意:给出三角形的三条边,问要让他组成三角形需要增加多少长度 题解:规律:如果给出的三条边不能组成三角形,那答 ...
- hdu1175连连看(dfs+细节)
连连看 Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submi ...
- jmeter3.0 java请求
1.java请求说明 需要压测某些java方法或一些请求需要通过编写代码实现 1.1.依赖jar包: jmeter下/lib/ext中的ApacheJMeter_java.jar(必须).Apache ...
- 使用httpClient获取请求cookie
package mytest; import java.util.ArrayList; import java.util.List; import org.apache.http.NameValueP ...
- Git 新建文件并提交
1.创建一个readme.txt. cd /home/cyp/learngit touch readme.txt vim readme.txt 编写内容, wq 保存推出 2.提交步骤 2.1 gi ...
- ServiceStack.Ormlit 事务
应该使用这个方法开启事务 public static IDbTransaction OpenTransaction(this IDbConnection dbConn) { return new Or ...
- Linux 150命令之 文件和目录操作命令 ls
文件和目录操作命令 ls 查看文件和目录查看显示详信息 ls 工具的参数 ls -l 查看文件详细信息 ls -h 查看文件的大小 ls -ld 只查看目录信息 ls –F 给不同文件加上不同标记 l ...
- 十三:Transparent Encryption in HDFS(转)
透明加密:http://blog.csdn.net/linlinv3/article/details/44963429 hadoop透明加密 kms 简介 Hadoop Key Manag ...
- 业务迁移---web
#本文是做记录使用,不做为任何参考文档# 迁移代码 将源代码scp至新的server上 搭建服务 yum安装nginx服务 yum install nginx #yum安装 service nginx ...
- 20162328蔡文琛week02
学号 20162328 <程序设计与数据结构>第2周学习总结 教材学习内容总结 这周学习了课本中的第二章内容,比起第一章,本章难度有略微底稿,从刚开始的显示字符转变为简单的加减乘除运算,经 ...