ccf 1246 最详细的菜鸟解析

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)]);
		
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_43797829/article/details/108686867