NOIP2022T1题解

[NOIP2022] 种花(民间数据)

题目描述

小 C 决定在他的花园里种出 \(\texttt{CCF}\) 字样的图案,因此他想知道 \(\texttt C\)\(\texttt F\) 两个字母各自有多少种种花的方案;不幸的是,花园中有一些土坑,这些位置无法种花,因此他希望你能帮助他解决这个问题。

花园可以看作有 \(n\times m\) 个位置的网格图,从上到下分别为第 \(1\) 到第 \(n\) 行,从左到右分别为第 \(1\) 列到第 \(m\) 列,其中每个位置有可能是土坑,也有可能不是,可以用 \(a_{i,j} = 1\) 表示第 \(i\) 行第 \(j\) 列这个位置有土坑,否则用 \(a_{i,j} = 0\) 表示这个位置没土坑。

一种种花方案被称为 \(\texttt{C-}\) 的,如果存在 \(x_1, x_2 \in [1, n]\),以及 \(y_0, y_1, y_2 \in [1, m]\),满足 \(x_1 + 1 < x_2\),并且 \(y_0 < y_1, y_2 \leq m\),使得第 \(x_1\) 的第 \(y_0\) 到第 \(y_1\) 、第 \(x_2\) 的第 \(y_0\) 到第 \(y_2\) 以及第 \(y_0\) 的第 \(x_1\) 到第 \(x_2\) 不为土坑,且只在上述这些位置上种花。

一种种花方案被称为 \(\texttt{F-}\) 的,如果存在 \(x_1, x_2, x_3 \in [1, n]\),以及 \(y_0, y_1, y_2 \in [1, m]\),满足 \(x_1 + 1 < x_2 < x_3\),并且 \(y_0 < y_1, y_2 \leq m\),使得第 \(x_1\) 的第 \(y_0\) 到第 \(y_1\) 、第 \(x_2\) 的第 \(y_0\) 到第 \(y_2\) 以及第 \(y_0\) 的第 \(x_1\) 到第 \(x_3\) 不为土坑,且只在上述这些位置上种花。

样例一解释中给出了 \(\texttt{C-}\) 形和 \(\texttt{F-}\) 形种花方案的图案示例。

现在小 C 想知道,给定 \(n, m\) 以及表示每个位置是否为土坑的值 \(\{a_{i,j}\}\)\(\texttt{C-}\) 形和 \(\texttt{F-}\) 形种花方案分别有多少种可能?由于答案可能非常之大,你只需要输出其对 \(998244353\) 取模的结果即可,具体输出结果请看输出格式部分。

对于所有数据,保证:\(1 \leq T \leq 5\)\(1 \leq n, m \leq 10^3\)\(0 \leq c, f \leq 1\)\(a_{i,j} \in \{0, 1\}\)

题解

观察数据范围,考虑\(O(nm)\)求解,我们发现,\(F\)型就是\(C\)型扩展,考虑先求解\(C\)

因为\(C\)型涉及到了三段\(0\),并且一个足够大的\(C\)能够通过上下两段的收缩来得到其他的合法方案,考虑对于枚举左上角,求解出合法的\(C\)

可以这样考虑,对于每一个左上角,需要往右和往下伸展,那么不妨用\(O(nm)\)的小\(DP\)求解出每一个点往右和往下最多伸展多少。由于这个\(C\)要求段的长度不能小于\(2\),而一个\(C\)型如果确定了竖着的一段,那么横着的那两段,设最长为\(x_1,x_2\)那么每一段都可以从\(2\)取到最大值构成合法的\(C\),总方案数就是\((x_1-1)(x_2-1)\),考虑直接在这个长度减一

\(S1_{x,y},S2_{x,y}\)分别表示点\((x,y)\)向左和向下最长段的长度\(-1\),可以采用如下\(DP\)

	//预处理S1
		for(int y=1;y<=m;y++){
			for(int x=n;x;--x){
				if(a[x][y]==1)s2[x][y]=-1;
				else s2[x][y]=s2[x+1][y]+1;
			}
		}
	//预处理S2
		for(int x=1;x<=n;x++){
			for(int y=m;y;--y){
				if(a[x][y]==1)s1[x][y]=-1;
				else s1[x][y]=s1[x][y+1]+1;
			}
		} 

那么考虑如何才能对一个点求出所有合法的\(C\),容易想到,总方案数中的\(x_1\)是确定的,我们可以将所有的\(x_2\)加上,很简单,再搞一个前缀和即可,设这个前缀和为\(S3\)

接着扩展到\(F\)\(F\)本质上是对\(C\)进行的扩展,可以求出一个\(C\)最多向下扩展多少,这就是这个\(C\)能够向下组合出\(F\)的总方案数,可以进行维护,就是再做一个前缀和\(S4\)\(S4_{x,y}\)即为对于\((x,y)\)向下扩展的每一个数的\(S2,S1\)的乘积,这样最终的答案即为

\[Ans_{C}=\sum_{x=1}^n\sum_{y=1}^m[a[x,y]\neq 1]S1[x,y]\times S3[x+2,y] \]

\[Ans_{F}=\sum_{x=1}^n\sum_{y=1}^m[a[x,y]\neq 1]S1[x,y]\times S4[x+2,y] \]

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 1050
#define p 998244353
int n,m,f1,f2,a[N][N],s1[N][N],s2[N][N],s3[N][N],s4[N][N],T,id;
int main(){
	ios::sync_with_stdio(false);
	cin>>T>>id;
	while(T--){
		memset(s1,-1,sizeof s1);
		memset(s2,-1,sizeof s2);
		memset(s3,0,sizeof s3);
		memset(s4,0,sizeof s4);
		cin>>n>>m>>f1>>f2;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				char x;
				cin>>x;
				a[i][j]=x-'0';
			}
		}
	//预处理S1
		for(int y=1;y<=m;y++){
			for(int x=n;x;--x){
				if(a[x][y]==1)s2[x][y]=-1;
				else s2[x][y]=s2[x+1][y]+1;
			}
		}
	//预处理S2
		for(int x=1;x<=n;x++){
			for(int y=m;y;--y){
				if(a[x][y]==1)s1[x][y]=-1;
				else s1[x][y]=s1[x][y+1]+1;
			}
		} 
	//预处理S3
		for(int y=1;y<=m;y++){
			for(int x=n;x;--x){
				if(a[x][y]==1)s3[x][y]=0;
				else s3[x][y]=s3[x+1][y]+s1[x][y];
			}
		}
	//预处理S4
		for(int y=1;y<=m;y++){
			for(int x=n;x;--x){
				if(a[x][y]==1)s4[x][y]=0;
				else s4[x][y]=(1ll*s4[x+1][y]+1ll*s1[x][y]*s2[x][y])%p;
			}
		} 
	//
		int ans1=0,ans2=0;
		for(int x=1;x<=n-2;x++){
			for(int y=1;y<=m;y++){
				if(a[x][y]==1)continue;
				ans1=(1ll*ans1+1ll*s1[x][y]*s3[x+2][y]*(a[x+1][y]!=1)%p)%p;
				ans2=(1ll*ans2+1ll*s1[x][y]*s4[x+2][y]*(a[x+1][y]!=1)%p)%p;
			}
		}
		cout<<1ll*ans1*f1%p<<" "<<1ll*ans2*f2%p<<endl;
	}
} 
posted @ 2022-11-30 22:51  spdarkle  阅读(243)  评论(0编辑  收藏  举报