P4967 黑暗打击 题解

本文介绍了一种使用矩阵加速和指数循环节定理解决洛谷P4967黑暗打击问题的方法。通过矩阵加速和指数循环节降低时间复杂度,实现了高效求解大规模递推问题。

P4967 黑暗打击 题解

题目

洛谷 P4967 黑暗打击

声明

由于本人实在是太菜,所以做这道题的时候还去看了好多相关的博客,也参考了洛谷上前几篇题解的一些思路,在此对写前几篇题解的巨佬表示感谢。

分析

这道题有很强的递推性,而数据范围又很大,所以我们首先想到了用矩阵加速来做这道题,但是 1 0 10000 10^{10000} 1010000 的数据范围还是会让朴素的矩阵过不了~~(何况你也不想去写高精吧)~~,所以我们还需要用到指数循环节定理,这样时间复杂度就能降能过的程度了。

过程

前置知识

我们先简简单单地讲一下解决本题需要运用到的矩阵加速以及指数循环节定理(如果您会可以直接跳过这一部分)。

矩阵加速

线性代数研究的向量多为列向量,根据这样的对矩阵乘法的定义方法,经常研究对列向量左乘一个矩阵的左乘运算,同时也可以在这里看出“打包处理”的思想,同时处理很多个向量内积。

矩阵乘法满足结合律,不满足一般的交换律。

利用结合律,矩阵乘法可以利用快速幂的思想来优化。

在比赛中,由于线性递推式可以表示成矩阵乘法的形式,也通常用矩阵快速幂来求线性递推数列的某一项。

——引用自oi-wiki

如上文所写,我们可以将每阶的“希尔伯特曲线土壤”存储在一个矩阵中,使用这个矩阵乘上状态转移矩阵的 n n n 次方就可以得到 n n n 阶“希尔伯特曲线土壤”的状态了。如果想要详细了解矩阵加速,请参阅oi-wiki,如果您看不懂也可以去搜博客。

欧拉函数

讲欧拉函数是为了方便各位理解指数循环节定理。最简单地来讲, n n n 的欧拉函数表示为 φ ( n ) \varphi(n) φ(n),其意义为 1 ∼ ( n − 1 ) 1\sim (n-1) 1(n1) 中与 n n n 互质的数的个数。如果要详细了解,我推荐这篇博客。

指数循环节

我们在这里直接放出公式,想要看详细证明可以看这篇博客。
a b ≡ a b % φ ( m ) + φ ( m ) ( m o d m ) a^b\equiv a^{b\%\varphi(m)+\varphi(m)}\pmod m abab%φ(m)+φ(m)(modm)

转移矩阵推导

为了推导这道题,我们先画出几张“希尔伯特曲线土壤”的图,其中蓝色表示水能淹到的区域,橙色表示不能淹到的区域(由于是用画图一个一个像素画的,所以每个格子的大小是和墙壁一样宽的,可能会出现一点变形。

由于前几个矩阵太小,不具有多少推导价值,所以我们从 4 4 4 阶开始。

4 4 4 阶希尔伯特曲线土壤

5 5 5 阶希尔伯特曲线土壤

6 6 6 阶希尔伯特曲线土壤

我们观察到,一个希尔伯特曲线土壤按照被淹的数量可以被分成大致四个部分。以 6 6 6 阶希尔伯特曲线土壤为例:

▓ 蓝色 上一阶希尔伯特曲线土壤中上面一半的部分

▓ 红色 上一阶希尔伯特曲线土壤中下面一半的部分

▓ 绿色 没有被灌水的部分

▓ 黄色 几块中间被灌满水的部分

我们可以发现,每个希尔伯特曲线土壤就可以由上一个希尔伯特曲线土壤转移而来,而每一小段黄色部分被灌水的面积就是 2 n 2^n 2n 2 n − 1 2^n-1 2n1

于是,我们用一个 1 × 4 1\times 4 1×4 的矩阵存储每阶希尔伯特曲线土壤的上面的一半下面的一半中间间隔的部分还有 1 1 1

什么?你问我为啥要浪费那一点点空间去存个 1 1 1?因为上面已经说过,每个黄色部分的长度不全是 2 n 2^n 2n。比如下图:

接下来,我们就可以运用前后两个希尔伯特曲线土壤的 1 × 4 1\times 4 1×4 矩阵来推出转移矩阵了。

我们设转移矩阵为 C C C,每个蓝色块为 a a a,每个红色块为 b b b,每个黄色块为 c c c ,则有:
[ a b c 1 ] × C = [ 3 a + 1 b + 1 c + − 1 2 a + 2 b + 3 c − 2 2 c 1 ] \begin{bmatrix}a&b&c&1\end{bmatrix}\times C=\begin{bmatrix}3a+1b+1c+-1&2a+2b+3c-2&2c&1\end{bmatrix} [abc1]×C=[3a+1b+1c+12a+2b+3c22c1]
于是,我们就可以算出 C C C 矩阵为:
[ 2 2 0 0 1 2 0 0 1 3 2 0 − 1 − 2 0 1 ] \begin{bmatrix} 2&2&0&0\\ 1&2&0&0\\ 1&3&2&0\\ -1&-2&0&1 \end{bmatrix} 2111223200200001

利用指数循环节降低时间复杂度

1 0 10000 10^{10000} 1010000 是无论用啥都存不下来的,但是我们有之前讲到的指数循环节定理,于是我们就只需要用快读一遍读入,一边判断 n n n 大于 9223372036854775783 9223372036854775783 9223372036854775783 就让 n n n 模上 9223372036854775783 − 1 9223372036854775783-1 92233720368547757831(因为 9223372036854775783 9223372036854775783 9223372036854775783 是质数,所以它与小于它的所有数互质,于是 φ ( 9223372036854775783 ) \varphi(9223372036854775783) φ(9223372036854775783) 就等于 9223372036854775783 − 1 9223372036854775783-1 92233720368547757831),读完了再加上 9223372036854775783 9223372036854775783 9223372036854775783 就行了。

代码

#include<bits/stdc++.h>
#define lll __int128//由于取模的数字本身就已经接近long long的上限,所以只能用int128来存矩阵 
#define ll long long
using namespace std;
const int MAXN=105;
long long MOD=9223372036854775783;
lll getnum()//快读 
{
	lll x=0;
	int f1=1 , f2=0;//f1是记录有没有负号的(虽然本题用不到),f2是记录有没有大于过MOD 
	char c=getchar();
	while(c<'0' || c>'9')
	{
		if(c=='-')
			f1=-1;
		c=getchar();
	}
	while(c>='0' && c<='9')
	{
		x=(x<<1) + (x<<3) + (c^48);//等同于x=x*10+c-'0' 
		if(x>MOD)//用指数循环节定理进行优化 
			f2=1 , x=x%(MOD-1);
		c=getchar();
	}
	return f1*x + f2*(MOD-1);//如果x大于过MOD即用过指数循环节定理,x就需要加上MOD的欧拉函数 
}
void putnum(lll a)//快写 
{
	if(!a)
	{
		putchar('0');
		return;
	}
	int t=1;
	while(t<=a)
		t*=10;
	do
	{
		t/=10;
		putchar('0' + a/t%10);
	}while(t!=1);
}
struct Matrix
{
	lll val[MAXN][MAXN];
	int n,m;//矩阵的大小 
	Matrix()
	{
		memset(val,0,sizeof val);//清空矩阵 
	}
	void buildstd(int N)
	{
		for(int i=1 ; i<=N ; i++)
			val[i][i]=1;
		m=N , n=N;
	}//建立单位矩阵(任何矩阵乘对应的单位矩阵都等于它本身) 
};
Matrix operator *(const Matrix a,const Matrix b)//重载运算符实现矩阵乘法 
{
	Matrix ret;
	for(int i=1 ; i<=a.m ; i++)
	{
		for(int j=1 ; j<=b.n ; j++)
		{
			for(int k=1 ; k<=a.n ; k++)
			{
				ret.val[i][j]=(ret.val[i][j] + (a.val[i][k]%MOD)*(b.val[k][j]%MOD)%MOD )%MOD;//为了不超出范围,每步都要取模 
				if(ret.val[i][j]<0)	ret.val[i][j]+=MOD;//注意这个地方,因为我们的矩阵里有负数,我们需要给负数加上模数(取模意义下等于加0)让它变成正数 
			}
		}
	}
	ret.n=a.m,ret.m=b.n;
	return ret;
}
int main()
{
	Matrix A,B,ANS;
	lll n;
	A.m=1,A.n=4;//初始化第一个希尔伯特曲线土壤 
	A.val[1][1]=0,A.val[1][2]=0,A.val[1][3]=1,A.val[1][4]=1;
	B.m=4,B.n=4;//初始化转移矩阵 
	B.val[1][1]= 2,B.val[1][2]= 2,B.val[1][3]=0,B.val[1][4]=0;
	B.val[2][1]= 1,B.val[2][2]= 2,B.val[2][3]=0,B.val[2][4]=0;
	B.val[3][1]= 1,B.val[3][2]= 3,B.val[3][3]=2,B.val[3][4]=0;
	B.val[4][1]=-1,B.val[4][2]=-2,B.val[4][3]=0,B.val[4][4]=1;
	n=getnum();
	if(n==1)//特判 
	{
		puts("1");
		return 0;
	}
	ANS.buildstd(4);//建立单位矩阵 
	do
	{
		if(n&1)
			ANS=ANS*B;
		B=B*B;
		n>>=1;
	}while(n);//矩阵快速幂 
	A=A*ANS;
	printf("%lld\n",(long long)(A.val[1][2]%MOD));//转化为long long输出
	return 0;
}
MATLAB主动噪声和振动控制算法——对较大的次级路径变化具有鲁棒性内容概要:本文主要介绍了一种在MATLAB环境下实现的主动噪声和振动控制算法,该算法针对较大的次级路径变化具有较强的鲁棒性。文中详细阐述了算法的设计原理与实现方法,重点解决了传统控制系统中因次级路径动态变化导致性能下降的问题。通过引入自适应机制和鲁棒控制策略,提升了系统在复杂环境下的稳定性和控制精度,适用于需要高精度噪声与振动抑制的实际工程场景。此外,文档还列举了多个MATLAB仿真实例及相关科研技术服务内容,涵盖信号处理、智能优化、机器学习等多个交叉领域。; 适合人群:具备一定MATLAB编程基础和控制系统理论知识的科研人员及工程技术人员,尤其适合从事噪声与振动控制、信号处理、自动化等相关领域的研究生和工程师。; 使用场景及目标:①应用于汽车、航空航天、精密仪器等对噪声和振动敏感的工业领域;②用于提升现有主动控制系统对参数变化的适应能力;③为相关科研项目提供算法验证与仿真平台支持; 阅读建议:建议读者结合提供的MATLAB代码进行仿真实验,深入理解算法在不同次级路径条件下的响应特性,并可通过调整控制参数进一步探究其鲁棒性边界。同时可参考文档中列出的相关技术案例拓展应用场景。
<think> 根据用户查询,用户想要查找洛谷题目P8506的题解。题目名为“标题计数”。 根据提供的引用内容,我们可以总结出题目的关键点:统计符合特定规则的标题行数。 题目要求:输入n行字符串,统计其中有多少行是符合规范的标题行。标题行的规则是: 1. 行首的非空格字符必须是'#'。 2. 在第一个'#'之后,必须紧跟着空格(即'#'后面是空格),然后后面可以有其他非空格字符(即标题内容)。 3. 如果第一个'#'后面直接跟着非空格字符(比如字母或其他),则不符合要求。 4. 如果第一个'#'后面只有空格和换行符(即没有其他非空格字符),也不符合要求。 引用[1]中给出了一种判断方法:判断'#'后是否为范围内的合法字符(字母或'#'),但这种判断可能不够全面。 引用[2]和引用[3]提供了更详细的思路和代码。 引用[2]的代码思路: - 读取整数n(表示行数),然后逐行处理。 - 对于每一行,设置一个标志flag,初始为0。 - 遍历该行的每个字符(直到倒数第二个字符,因为要检查下一个字符): * 如果flag已经为1(表示已经遇到了一个'#'且后面跟着空格),并且当前字符不是空格(说明出现了非空格字符,即标题内容),则计数ans++,并跳出该行的循环(因为已经满足条件)。 * 如果当前字符既不是'#'也不是空格,则说明在遇到'#'之前就出现了非空格字符(不符合规则),直接跳出循环(该行不计入)。 * 如果当前字符是'#',则检查下一个字符是否为空格: - 如果是空格,则设置flag=1(表示已经找到符合条件的起始标志,但还要看后面是否有非空格字符); - 如果不是空格,则跳出循环(该行不符合)。 - 注意:该代码在判断到非空格字符并计数后立即跳出该行的循环,避免重复计数。 引用[3]的思路: - 忽略行首空格,找到第一个'#'。 - 如果第一个'#'后面紧跟着非空格字符,则不符合条件(排除情况7,8)。 - 如果符合条件(即'#'后是空格),则设置flag=true,然后继续遍历。 - 在后续遍历中,如果flag为true并且遇到了非空格字符(即标题内容),则计数;如果flag为false就遇到非空格字符,则不符合(排除情况6,9)。 综合以上,我们可以给出题解题解代码(基于引用[2]的代码,并做适当解释): </think> ### P8506 标题计数题解 本题需要统计输入文本中符合规范的标题行数量。标题行的规则: 1. **首字符规则**:第一个非空格字符必须是 `#` 2. **空格要求**:`#` 后必须紧跟至少一个空格(例如 `# 标题` 合法,`#标题` 非法) 3. **内容要求**:空格后必须存在至少一个非空格字符(例如 `# ` 后无内容则非法) #### 参考代码(C++实现) ```cpp #include <cstdio> #include <cstring> using namespace std; int main() { int n, ans = 0; scanf("%d", &n); getchar(); // 吸收换行符 while (n--) { char line[1000]; fgets(line, sizeof(line), stdin); // 安全读取整行 int flag = 0; // 标记是否已遇到合法"# " bool valid = false; // 标记当前行是否合法 for (int i = 0; line[i] != '\0'; i++) { if (line[i] == ' ') continue; // 跳过前导空格 // 遇到非空格字符 if (line[i] == '#') { if (line[i+1] == ' ') flag = 1; // 满足条件2:'#'后是空格 else break; // '#'后无空格,非法 } else { if (flag == 1) valid = true; // 满足条件3:空格后有内容 break; // 其他情况均非法 } } if (valid) ans++; } printf("%d\n", ans); return 0; } ``` #### 算法解析 1. **输入处理** - 读取行数 `n` 后需用 `getchar()` 吸收换行符,避免影响后续行读取[^2]。 - 使用 `fgets` 安全读取含空格的整行字符串(替代已废弃的 `gets`)[^2]。 2. **核心逻辑** - **跳过前导空格**:忽略行首的所有空格字符。 - **检查 `#` 位置**:首个非空格字符必须是 `#`,否则非法(如 `A#`)。 - **检查空格要求**:`#` 后必须紧跟空格(如 `#x` 非法)。 - **检查标题内容**:空格后需有非空格字符(如 `# ` 后无内容非法)[^3]。 3. **终止条件** - 遇到非法字符立即终止当前行检测(如 `#x` 在 `x` 处终止)。 - 仅当所有条件满足时,计数器 `ans` 增加。 #### 复杂度分析 - **时间复杂度**:$O(n \times L)$,$n$ 为行数,$L$ 为平均行长。 - **空间复杂度**:$O(L)$,仅需存储单行字符串。 #### 边界用例验证 | 输入行 | 是否合法 | 说明 | |----------------|-----------|--------------------------| | `"# Title"` | ✔️ | 完全符合规范 | | `" # Title"` | ✔️ | 前导空格不影响 | | `"#Title"` | ❌ | `#` 后无空格 | | `"# "` | ❌ | 空格后无内容 | | `"A# Title"` | ❌ | 首字符非 `#` |
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值