2019牛客暑期多校训练营(第二场)----E-MAZE

本文介绍了一种使用动态规划和矩阵快速幂解决网格路径计数问题的方法,特别适用于起点和终点行数固定的复杂网格环境。通过构建二维数组表示地图,并利用dp公式计算路径数,最终将问题转化为矩阵乘法,实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/882/D
来源:牛客网
涉及:线段树,矩阵快速幂

点击这里回到2019牛客暑期多校训练营解题—目录贴


题目如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这是一个进阶版的走网格,但是现在是从上面往左右或者下方走,而且起点和终点是不固定,但起点和终点所在的行数是固定的,同时,还有一些地方是不能走的

很明显,变了样子,还是变不了dp的方法。仍然是从dp的方向考虑问题


我们主要考虑行与行之间的关系,很明显,下一行的某个位置子肯定是由上一行的一些位置走过有来的。

假设整个地图我们存在一个二维数组 m a p map map(这个 m a p map map是数组的名字)。于是
m a p [ i ] [ j ] = 0 map[i][j]=0 map[i][j]=0说明此位置是可以通过的,否则不能通过。

我们假设 d p [ i ] [ j ] dp[i][j] dp[i][j]代表经过 m a p [ i − 1 ] [ j ] map[i-1][j] map[i1][j]到达 m a p [ i ] [ j ] map[i][j] map[i][j]位置的路径数

于是可以得到dp式
d p [ i ] [ j ] = { 0 m a p [ i − 1 ] [ j ] = 1 ∑ k = a b d p [ i − 1 ] [ k ] &ThickSpace; ( a &lt; j &lt; b &ThickSpace; 且 ∑ k = a b m a p [ i − 1 ] [ k ] = 0 ) m a p [ i − 1 ] [ j ] = 0 dp[i][j]=\begin{cases}0&amp;map[i-1][j]=1\\\sum_{k=a}^bdp[i-1][k]\;(a&lt;j&lt;b\;且\sum_{k=a}^bmap[i-1][k]=0)&amp;map[i-1][j]=0\end{cases} dp[i][j]={0k=abdp[i1][k](a<j<bk=abmap[i1][k]=0)map[i1][j]=1map[i1][j]=0

解释一下dp式是啥意思:
如图所示是第一行的map值
在这里插入图片描述
如图所示,先不管 m a p [ i 2 ] [ j 5 ] map[i_2][j_5] map[i2][j5]是不是1

由于 m a p [ i 1 ] [ j 2 ] map[i_1][j_2] map[i1][j2] m a p [ i 1 ] [ j 9 ] map[i_1][j_9] map[i1][j9]都为1(无法通过),所以只有上图六个颜色为绿色的位置才能到达 m a p [ j 2 ] [ j 5 ] map[j_2][j_5] map[j2][j5]的位置(经过 m a p [ i 1 ] [ j 5 ] map[i_1][j_5] map[i1][j5]),故 d p [ i 2 ] [ j 5 ] = ∑ k = 2 5 d p [ i 1 ] [ j 6 ] dp[i_2][j_5]=\sum_{k=2}^5dp[i_1][j_6] dp[i2][j5]=k=25dp[i1][j6]

同理 d p [ i 2 ] [ j 1 ] = d p [ i 1 ] [ j 1 ] dp[i_2][j_1]=dp[i_1][j_1] dp[i2][j1]=dp[i1][j1]


于是发现
d p [ i ] [ 1 ] , d p [ i ] [ 2 ] , d p [ i ] [ 3 ] , . . . , d p [ i ] [ m ] dp[i][1],dp[i][2],dp[i][3],...,dp[i][m] dp[i][1],dp[i][2],dp[i][3],...,dp[i][m]可以由
d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 2 ] , d p [ i − 1 ] [ 3 ] , . . . , d p [ i − 1 ] [ m ] dp[i-1][1],dp[i-1][2],dp[i-1][3],...,dp[i-1][m] dp[i1][1],dp[i1][2],dp[i1][3],...,dp[i1][m]来线性表示

涉及线性,且m比较小,可以用矩阵来做。每两行之间都会有dp值的转移矩阵

而且从 d p [ i ] dp[i] dp[i] d p [ i + 1 ] dp[i+1] dp[i+1]的转移矩阵只与 m a p map map有关
假设map第 i i i行为
在这里插入图片描述
那么 d p [ i ] dp[i] dp[i] d p [ i + 1 ] dp[i+1] dp[i+1]的转移矩阵为
[ 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 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 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 ] \begin{bmatrix}0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;1&amp;1&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;1&amp;1&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;0&amp;1&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;1&amp;1\\0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;1&amp;1\end{bmatrix} 0000000001100000011000000000000000001000000000000000001100000011
满足

[ d p [ i + 1 ] [ 1 ] d p [ i + 1 ] [ 2 ] d p [ i + 1 ] [ 3 ] d p [ i + 1 ] [ 4 ] d p [ i + 1 ] [ 5 ] d p [ i + 1 ] [ 6 ] d p [ i + 1 ] [ 7 ] d p [ i + 1 ] [ 8 ] ] T = [ d p [ i ] [ 1 ] d p [ i ] [ 2 ] d p [ i ] [ 3 ] d p [ i ] [ 4 ] d p [ i ] [ 5 ] d p [ i ] [ 6 ] d p [ i ] [ 7 ] d p [ i ] [ 8 ] ] T [ 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 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 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 ] {\begin{bmatrix}dp[i+1][1]\\dp[i+1][2]\\dp[i+1][3]\\dp[i+1][4]\\dp[i+1][5]\\dp[i+1][6]\\dp[i+1][7]\\dp[i+1][8]\end{bmatrix}}^{T}={\begin{bmatrix}dp[i][1]\\dp[i][2]\\dp[i][3]\\dp[i][4]\\dp[i][5]\\dp[i][6]\\dp[i][7]\\dp[i][8]\end{bmatrix}}^{T}\begin{bmatrix}0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;1&amp;1&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;1&amp;1&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;0&amp;1&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;1&amp;1\\0&amp;0&amp;0&amp;0&amp;0&amp;0&amp;1&amp;1\end{bmatrix} dp[i+1][1]dp[i+1][2]dp[i+1][3]dp[i+1][4]dp[i+1][5]dp[i+1][6]dp[i+1][7]dp[i+1][8]T=dp[i][1]dp[i][2]dp[i][3]dp[i][4]dp[i][5]dp[i][6]dp[i][7]dp[i][8]T0000000001100000011000000000000000001000000000000000001100000011


如何得到map中某一行的转移矩阵:

转移矩阵中第i列控制着dp第i列的值。

对于一个dp[i][j],我们只要看map[i-1]的哪些地方可以到达map[i-1][j]。可以分别从map[i-1][j]向左和向右遍历

求第x行的转移矩阵,参考代码:

for(int i=1;i<=m;i++){
	if(map[x][i]==1)	continue;//如果map[x][i]本身就是1,那么一定不能到达下一行。
	else tree[k].mul.trmap[i][i]=1;//否则可以,然后向左和向右遍历。
	for(int j=i+1;j<=m;j++){
		if(map[x][j]==0)	tree[k].mul.trmap[j][i]=1;
		else break;
	}
	for(int j=i-1;j>0;j--){
		if(map[x][j]==0)	tree[k].mul.trmap[j][i]=1;
		else break;
	}
}

每一行的转移矩阵求出来,假设第i行的转移矩阵为 M i M_i Midp[1]的所有值我们也知道(从map[1][a]出发那么dp[1][a]=1,其他dp[1]全为0)

假设终点为map[n][b],答案即为dp[n+1][b](注意dp[n+1][b]为经过map[n][b]到达map[n+1][b]的路径数),而不是dp[n][b]。

于是
[ d p [ n + 1 ] [ 1 ] d p [ n + 1 ] [ 2 ] d p [ n + 1 ] [ 3 ] d p [ n + 1 ] [ 4 ] d p [ n + 1 ] [ 5 ] d p [ n + 1 ] [ 6 ] d p [ n + 1 ] [ 7 ] d p [ n + 1 ] [ 8 ] ] T = [ d p [ 1 ] [ 1 ] d p [ 1 ] [ 2 ] d p [ 1 ] [ 3 ] d p [ 1 ] [ 4 ] d p [ 1 ] [ 5 ] d p [ 1 ] [ 6 ] d p [ 1 ] [ 7 ] d p [ 1 ] [ 8 ] ] T M 1 M 2 . . . M n {\begin{bmatrix}dp[n+1][1]\\dp[n+1][2]\\dp[n+1][3]\\dp[n+1][4]\\dp[n+1][5]\\dp[n+1][6]\\dp[n+1][7]\\dp[n+1][8]\end{bmatrix}}^{T}={\begin{bmatrix}dp[1][1]\\dp[1][2]\\dp[1][3]\\dp[1][4]\\dp[1][5]\\dp[1][6]\\dp[1][7]\\dp[1][8]\end{bmatrix}}^{T}M_1M_2...M_n dp[n+1][1]dp[n+1][2]dp[n+1][3]dp[n+1][4]dp[n+1][5]dp[n+1][6]dp[n+1][7]dp[n+1][8]T=dp[1][1]dp[1][2]dp[1][3]dp[1][4]dp[1][5]dp[1][6]dp[1][7]dp[1][8]TM1M2...Mn

同时发现从map[1][a]到map[n][b]的路径数其实就是所有转移矩阵相乘后(假设为M)M[a][b]的值(初始dp[1]向量中只有第a个值为1,相当于选择M矩阵的第a行,到达map[n][b]就是选取第b列)


每次修改map[x][j]的状态,相当于修改转移矩阵 M x M_x Mx(第x行)

可以用线段树动态维护矩阵的乘积。注意取模和矩阵乘法

矩阵乘法(运算符重载)

struct array{
	ll trmap[maxm][maxm];
	array operator*(array &ar){
		array x;
		memset(x.trmap,0,sizeof(x.trmap));
		int i,j,k;
		for(i=1;i<=m;i++)
			for(j=1;j<=m;j++)
				for(k=1;k<=m;k++)
					x.trmap[i][j]=(x.trmap[i][j]+this->trmap[i][k]*ar.trmap[k][j])%mod;
		return x;
	}
}; 

代码如下:

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
//三个最值
const int maxn=50005;
const int maxm=12;
const int mod=1e9+7;
//n,m,q为题目所给变量,v用来创建线段树的叶子节点(表示现在该创建第v个转移矩阵叶子节点)
int n,m,q,v=1; 
int map[maxn][maxm];//地图,只存0和1
struct array{//矩阵结构体
	ll trmap[maxm][maxm];
	array operator*(array &ar){//运算符重载,方便矩阵相乘
		array x;
		memset(x.trmap,0,sizeof(x.trmap));
		int i,j,k;
		for(i=1;i<=m;i++)
			for(j=1;j<=m;j++)
				for(k=1;k<=m;k++)
					x.trmap[i][j]=(x.trmap[i][j]+this->trmap[i][k]*ar.trmap[k][j])%mod;
		return x;
	}
}; 
struct op{//线段树节点
	int l,r;
	array mul;
};
op tree[4*maxn];
void build(int l,int r,int k){//创建线段树
	tree[k].l=l;
	tree[k].r=r;
	if(l==r){//l与r相等时就创建叶子节点
		for(int i=1;i<=m;i++){
			if(map[v][i]==1)	continue;//如果map[x][i]本身就是1,那么一定不能到达下一行。
			else tree[k].mul.trmap[i][i]=1;//否则可以,然后向左和向右遍历。
			for(int j=i+1;j<=m;j++){
				if(map[v][j]==0)	tree[k].mul.trmap[j][i]=1;
				else break;
			}
			for(int j=i-1;j>0;j--){
				if(map[v][j]==0)	tree[k].mul.trmap[j][i]=1;
				else break;
			}
		}
		v++;//v++表示下一个该创建第v个转移矩阵叶子节点
		return;
	}
	int pos=(l+r)/2;
	build(l,pos,2*k);
	build(pos+1,r,2*k+1);
	tree[k].mul=tree[2*k].mul*tree[2*k+1].mul;//状态合并
	return;
}
void add(int x,int k){//单点修改
	if(tree[k].l==tree[k].r){
		memset(tree[k].mul.trmap,0,sizeof(tree[k].mul.trmap));//先初始化为0
		for(int i=1;i<=m;i++){//根据新的map值重新创建当前转移矩阵叶子节点
			if(map[x][i]==1)	continue;
			else tree[k].mul.trmap[i][i]=1;
			for(int j=i+1;j<=m;j++){
				if(map[x][j]==0)	tree[k].mul.trmap[j][i]=1;
				else break;
			}
			for(int j=i-1;j>0;j--){
				if(map[x][j]==0)	tree[k].mul.trmap[j][i]=1;
				else break;
			}
		}
		return;
	} 
	int pos=(tree[k].l+tree[k].r)/2;
	if(x<=pos)	add(x,2*k);
	else	add(x,2*k+1);
	tree[k].mul=tree[2*k].mul*tree[2*k+1].mul;//状态合并
	return;
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	int i,j;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)	scanf("%1d",&map[i][j]);//注意一个个输入,所以用%1d
	build(1,n,1);//创建线段树
	while(q--){
		int p,a,b;
		scanf("%d%d%d",&p,&a,&b);
		if(p==1)	map[a][b]^=1,add(a,1);//先修改map值,再根据新map值修改转移矩阵叶子节点
		else	printf("%lld\n",tree[1].mul.trmap[a][b]);//tree[1]存的是全部转移矩阵的乘积
	}	
	return 0;
} 
内容概要:该研究通过在黑龙江省某示范村进行24小时实地测试,比较了燃煤炉具与自动/手动进料生物质炉具的污染物排放特征。结果显示,生物质炉具相比燃煤炉具显著降低了PM2.5、CO和SO2的排放(自动进料分别降低41.2%、54.3%、40.0%;手动进料降低35.3%、22.1%、20.0%),但NOx排放未降低甚至有所增加。研究还发现,经济性和便利性是影响生物质炉具推广的重要因素。该研究不仅提供了实际排放数据支持,还通过Python代码详细复现了排放特征比较、减排效果计算和结果可视化,进一步探讨了燃料性质、动态排放特征、碳平衡计算以及政策建议。 适合人群:从事环境科学研究的学者、政府环保部门工作人员、能源政策制定者、关注农村能源转型的社会人士。 使用场景及目标:①评估生物质炉具在农村地区的推广潜力;②为政策制定者提供科学依据,优化补贴政策;③帮助研究人员深入了解生物质炉具的排放特征和技术改进方向;④为企业研发更高效的生物质炉具提供参考。 其他说明:该研究通过大量数据分析和模拟,揭示了生物质炉具在实际应用中的优点和挑战,特别是NOx排放增加的问题。研究还提出了项具体的技术改进方向和政策建议,如优化进料方式、提高热效率、建设本地颗粒厂等,为生物质炉具的广泛推广提供了可行路径。此外,研究还开发了一个智能政策建议生成系统,可以根据不同地区的特征定制化生成政策建议,为农村能源转型提供了有力支持。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值