ccf 1246
一百分的代码咱就不奢望了,送上九十六分代码,供大家参考参考。
题目链接:http://118.190.20.162/view.page?gpid=T102
观察测试点知道,大多测试点的s都只有一位或者两位。最后一个测试点,赞就直接放弃了。
观察测试点和字符串长度可以知道,当n足够大的时候,字符串长度会变得巨大,暴力方法肯定不行。所以需要一定的规律。
观察得知,在字符串中,s为一位或两位的情况只有14种。
一位:1,2,4,6
两位:16,26,41,42,44,46,61,62,64,66
再观察字符串,可以得到一个这样的关系图:(懒得画了,直接用了另一个博主的图,链接:https://www.pythonheidong.com/blog/article/433221/)
通过这样的一个关系图,我们能得到上一个串和下一个串的递推关系。思路雏形就出来了,利用动态规划应该可以解决这个问题。
两位数分叉原理:比如16,就分为2的一次方和2的六次方。
通过这个图我们可以知道:
下一个串的2可以由上一个串的1得到
下一个串的4可以由上一个串的2得到
下一个串的1和6可以由上一个串的4得到
依此类推,我们就可以得到s为一位的情况下的递推关系。
接下里就是难点一,如何得到s是两位的情况下的递推关系。
其实我们把上面的图变通一下,把本来不相邻的组合起来,发现也可以得到同样的效果。
例如:16可以产生264,划分成(26)4,既可以得到26.
这里需要解决的思想难题是,为什么264只计一个26,为什么不计其他的组合方式呢。
因为其他组合方式在s为一位的时候就已经被计数了,再计的话,就会出现重复,而26是由两个不同的两个数组合而来,在s为一位时不会被计数。
理解了上面的思想,我们就能得到对照表1:
其表达的意义是:上一个串中的1的数量等于下一个串中2的数量。即上一个串中的1通过上诉关系变成了下一个串中的2.
理解了对照表,我们就需要从对照表中提取递推关系。并进行编号处理,得到递推关系表。
通过对照表,我们可以得到如下的递推表,然后对递推表进行编号。
上图递推表告诉我们:
下一个串中的1是由上一个串中的4得到的。
下一个串中的4是由上一个串中的4,6得到的。
通过递推表和数字编号,得到递推关系:
类似:
f(i)(1)=f(i-1)(3)
f(i)(2)=f(i-1)(1)
f(i)(3)=f(i-1)(2)+f(i-1)(4)
f(i)(4)=f(i-1)(3)+f(i-1)(4)
…
到这里,串中各个数字的递推关系就得到了,如何处理递推变成了当前面临的问题。
先举一个简单例子,斐波那契数列,就是个十分经典的递推关系。
f(n)=f(n-1)+f(n-2)
如果把【f(n-1),f(n-2)】看作是一个向量,那这个向量乘以矩阵【【1,1】【1,0】】则可以得到新的向量【f(n),f(n-1)】.。
这就是利用矩阵来推进递推关系。也就是,把递推的上一个状态,乘以一个矩阵,得到递推的下一个状态。这个思想就是破题关键点之一。
然后我们理解一下破题的下一个关键点。就是快速幂。要求2^16,我们也许回想,把16个2相乘。这样固然能得到结果,但是当次方数足够大时,这样的计算方式就十分拉跨。于是我们引入快速幂。
当前两个2相乘时,我们就把原题变成了4^8。重复这样的操作,就可以减少大量的运算。当幂变了奇数时,就单独拿一个幂出来,再将剩下的偶数幂继续进行快速幂运算,最后把拿出来的奇数幂乘回去。
了解了矩阵递推思想和快速幂思想,结合上诉的递推关系式,就可以进行计算了。
当然,难点在于递推关系到矩阵的转换过程,这个过程没办法帮忙,需要自己理解。
这就是14为递推关系对应的dp矩阵。注意,这个dp矩阵的向量方向时纵向的。)
初始结果集:
利用矩阵乘法的结合新,我们可以用快速幂的方法,求出所有矩阵的乘积,再把算出的矩阵乘进结果集,直接得结果。如果中间有奇数幂出现,就取出奇数幂先乘进结果集中,剩下的偶数幂继续进行快速幂运算。
当然,直接进行14位的矩阵递推可能不好理解,可以用
四位的矩阵和四位的结果集来进行递推理解,再拓展到14位上。
上代码:
import java.util.Scanner;
import java.util.Map;
import java.util.HashMap;;
//快速幂+dp递推矩阵
class Matrix{
public long[][] Ma_Ma(long [][] dp){//类似这种值很大的题目,尤其需要关注结果范围和考虑溢出。
int n=dp.length;
long [][] temp=new long[n][n];
//两个相同的矩阵相乘,dp递推矩阵相乘
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
for(int k=0;k<n;k++) {
temp[i][j]+=(dp[i][k]*dp[k][j]);
temp[i][j]%=998244353;//结果太大,如果不进行取余操作,会结果溢出,出现负值
}
}
}
return temp;
}
public long[] Ma_Ve(long [][] dp,long []res) {
int n=dp[0].length;//矩阵的列数
int m=res.length;
long [] temp=new long[m];
//结果向量与矩阵相乘
for(int i=0;i<n;i++) {
for(int j=0;j<m;j++) {
temp[i]+=(res[j]*dp[i][j]);
temp[i]%=998244353;//结果太大,如果不进行取余操作,会结果溢出,出现负值
}
}
return temp;
}
}
public class DP {
public static void main(String[] args) {
//初始的结果向量(n=0)时。
long [] res =new long[14];
res[0]=1;
//dp矩阵,矩阵向量方向是行方向,第一次因为乘了列方向的向量,导致错误。
long [][] dp=
{{0,0,1,0,0,0,0,0,0,0,0,0,0,0},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,0,1,0,0,0,0,0,0,0,0,0,0},
{0,0,1,1,0,0,0,0,0,0,0,0,0,0},
{0,0,1,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,1,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,1,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,1,0,0},
{0,0,0,0,0,1,0,0,0,0,0,0,0,1},
{0,0,0,0,0,0,0,0,1,0,0,0,0,0},
{0,0,0,0,0,0,1,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,1,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,0,0,0,0}
};
//对照表
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
map.put(1,0);
map.put(2,1);
map.put(4,2);
map.put(6,3);
map.put(16,4);
map.put(26,5);
map.put(41,6);
map.put(42,7);
map.put(44,8);
map.put(46,9);
map.put(61,10);
map.put(62,11);
map.put(64,12);
map.put(66,13);
Scanner in=new Scanner(System.in);
int n=in.nextInt();
int s=in.nextInt();
in.close();
Matrix ma=new Matrix();
//快速幂核心代码
//利用矩阵乘法的交换性和结和性。先算后面所有矩阵的矩阵乘法,把最后得到的矩阵乘入结果向量,得到最终结果。
//快速幂思想,就是利用上一次的计算结果作为新的基数,使得计算技术指数式减少。
//当遇见幂数为奇数时,就先取出一个乘进结果集,剩下的偶数幂再进行快速幂运算
while(n>0) {
if(n%2==1) {
res=ma.Ma_Ve(dp, res);
}
dp=ma.Ma_Ma(dp);
n>>=1;//移位运算
}
System.out.print(res[map.get(s)]);
}
}