摩天楼

摩天楼

题目描述

奶牛公司打算建造一栋摩天楼,共有B层。现在已经建好了第一层,第一层有N个房间,这N个房间从左往右排成一行,第i个房间的号码是A[i],其中1<=A[i]<=9。为了节约成本,公司复制了第一层,然后在第一层上方粘贴了B-1次,这样就能快速完工。由于是复制粘贴的,所以这B层楼完全相同。
FJ对数字很着迷,于是它从第1层楼选择某个房间的号码,再从第2层楼选择某个房间的号码.....最后从第B层楼选择某个房间的号码。FJ把这B个数字从左往右排成一行,就构成了一个大整数,不妨设该整数是X。
请问有FJ有多少种不同的选取方案,使得X % Y = Z。其中Y和Z是输入数据给定的。
由于合法要求的方案数太多,你只需要输出模1000000007结果。

输入格式 2217.in

第一行,四个整数:N,B,Z,Y。
2 <= N <= 50000,1 <= B <= 10^9,0 <= Z < Y<=100, Y >= 2。
第二行,N个整数,第i个整数是A[i]。 1 <= A[i] <=9。

输出格式 2217.out

一个整数。

输入样例 2217.in

输入样例一:
12 1 5 10
3 5 6 7 8 9 5 1 1 1 1 5

输入样例二:
3 2 1 2
6 2 2

输入样例三:
3 2 1 2
3 1 2

输出样例 2217.out

输出样例一:
3
输出样例二:
0
输出样例三:
6

       

    首先,这里的数是一层层选的,具有明显阶段性,显而易见是一道DP。

    设f[i][j]表示选了前i层,组成的数%y的余数为j的方案数。

    我们需要枚举这层选的数k。如果枚举for循环1~n逐一枚举a[ ],效率就太低了。实际上虽然题目中的N很大,但a[ ]的值只有1到9,且构成的数的方案数只与其值、个数有关。因此只需要在一开始用一个一维数组保存1~9的个数。选数时枚举1~9即可。

    边界条件:f[0][0]=1;

    状态转移方程:(枚举上一次的余数为j)

    f[i][(j*10+k)%y]+=f[i-1][j];  (0<k<10,且k存在于a[]中)

    上述做法的时间复杂度:O(b*y*9),显然承受不了,只有40分。

    想要AC,需要进一步的优化。

    上述的转移方程,已经没有优化的余地了。我们发现,以上的递推为线性常系数递推。因此,可以使用矩阵乘法来优化。

    [关于矩阵乘法的基本知识及原理,此处不细说,详见我的另一篇博客/总结:矩阵乘法]

    根据上述的转移方程不难发现。每一个余数j,都有其唯一对应的“目标”(j*10+k)%y。矩阵乘法的原理在于,将原本的值,乘上这个矩阵,得出下一状态我们想要的值。

    那么,构造矩阵的诀窍何在呢?先通过举例分析,可能较为直观。

    我们以每一个f[i][     ],为一个矩阵。

    f[0]的矩阵为{1,0,0,0}(分别表示f[0][0],f[0][1],f[0][2],f[0][3]的值)

   (根据矩阵乘法的规则,应该构造一个4*4的矩阵,用f[0][   ]矩阵去乘它,才能得到f[1][   ]这样1*4新矩阵)

    假设,给出的a[i]有2,4。

    则如果这层选择2,则f[1][(0*10+2)%y]+=f[0][0]。

    如果这层选择4,则f[1][(0*10+4)%y]+=f[0][0];

    因此,矩阵中,对应的(0 , 2)值应为1。

    以此类推,一个余数j 后面放上一个值k,得到一个目标target。则矩阵中(target,j)位置的值应为num[k]。(即为k的个数,这是因为同一层,选择不同位置的同一种数,视为不同的方案)。矩阵中的其余位置,都应为0。

    因此,输入之后,根据题目中的k、y就可以得出所有余数的“目标”,从而得到矩阵。

    然后,进行矩阵乘法的“快速幂”操作。(即将b个矩阵进行矩阵乘法)最后再将f[0][  ]这个初始值“幂”的结果再进行一次矩阵乘法,即为答案。

    说了那么多,实际上,我认为,构造矩阵的关键就在于,由递推公式,得到新状态的值应包含的旧状态的值及其参数,从而构造矩阵。只要递推公式正确,同时熟悉矩阵乘法的操作流程,构造矩阵并不是一件难事。

    再说点挺重要的题外话吧。原本我的代码的函数部分是这样的:


    看起来没问题,但是造成了爆栈,40分。

    这是因为栈中空间很有限,只有几兆。我定义在递归函数中的变量达到了上限,因此爆栈。

    于是,我将递归中的变量,全都改成了全局变量。但是一点改变都没有,仍是爆栈。

    老师和同学继续帮我看,同样百思不得其解。尝试将run的参数改成引用,或者是将结构体中的数改成int类型,居然就能正常运行了。

    于是老师只能得出一个结论:在递归中调用函数,它可能会在栈中进行一次备份,占了栈中太多空间,导致爆栈。

    终于,这个很迷的问题得到了解释:首先,递归中不要定义上兆的变量。除此之外,递归中调用的函数的参数占空间应尽可能小。可以用引用 & 代替原本的方式。如果觉得引用会互相影响,那就传硕小的参数……

    于是最后的代码改成了这样:

#include
   
   
    
    
#include
    
    
     
     
using namespace std;
long long n,b,z,y,mod=1e9+7;
long long num[10],p;
struct wyy
{
	long long a[105][105];
}every,ori,temp,ans,res,an[35];
wyy run(int u,int v)
{
	for(int i=0;i
     
     
      
      >n>>b>>z>>y;//%y==z 
	for(int i=1;i<=n;i++)	
	{
		int x;
		cin>>x;
		num[x]++;
	}
	for(int i=0;i
      
      
     
     
    
    
   
   

    Run函数的参数,变成传一个下标,而不是一个结构体变量。于是终于成功AC。

    最后感谢昨晚耐心帮助的JT老师、YWQ、LHF~(鞠个躬~)。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值