[NOIP2022] 种花 题解
前言
由于疫情反弹,北京的 NOIp 取消了。在家里做的时候,感觉这题挺有意思的,就有了这篇题解。
题意简述
题目链接
-
给定一个 01 矩阵。
-
求在这个矩阵中
C和F这两种图形的数量。图形的上底和下底的间隔至少一行,并且图形覆盖范围全部是0,上底下底长度至少为 2。具体定义见题目。 -
数据范围:
1 \leq T \leq 5 ,1 \leq n, m \leq 10^3 。
题目分析
看到题目很明显是一道计数 DP,由数据范围可以推断出我们需要 F 就是在 C 后面加一段“尾巴”,所以对于每个 C 的方案乘上可以加的“尾巴”的长度,就是增加的 F 的方案数。
定义数组 1 阻隔的 0 的个数,即以这个点为左端点取一行的方案数,考虑用双指针求出每一行的 1 阻隔的 0 的个数,用同样的方法预处理。
考虑枚举图形的左上角 (x1,y0),再枚举图形中间的横的左端点 (x2,y0)。易得出,新增 C 的方案数为 F 的方案数为
对于每个左上角,它能产生 C 的方案数是:
考虑提出
前缀和维护即可,对于 F 的方案数,维护
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5,Mod=998244353;
#define int long long
int a[N][N],n,m,c,f;
int fx[N][N];//第i行从第j列开始的方案数
int fy[N][N];//从第i行第j列往后数0的个数
int sum[N][N],su[N][N];
int anc,anf,T,idd;
char x;
signed main(){
cin>>T>>idd;
while(T--){
scanf("%d%d%d%d",&n,&m,&c,&f);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
cin>>x;
if(x=='0'){
a[i][j]=0;
}else{
a[i][j]=1;
}
}
}
if(c==0&&f==0){
cout<<"0 0\n";
continue;
}
int l,r;
memset(fx,0,sizeof fx);
memset(fy,0,sizeof fy);
//预处理
for(int i=1;i<=n;++i){
l=r=1;
while(l<m){
for(;!a[i][r]&&r<=m;++r);
//cout<<r<<endl;
while(l<r){
fx[i][l]=r-l-1;
++l;
}
l+=1;
r=l;
}
}
for(int j=1;j<=m;++j){
l=r=1;
while(l<n){
for(;!a[r][j]&&r<=n;++r);
while(l<r){
fy[l][j]=r-l-1;
++l;
}
l+=1;
r=l;
}
}
anc=anf=0;
for(int j=1;j<=m;++j){
for(int i=1;i<=n;++i){
sum[i][j]=sum[i-1][j]+fx[i][j];
sum[i][j]%=Mod;
}
}
for(int j=1;j<=m;++j){
for(int i=1;i<=n;++i){
su[i][j]=su[i-1][j]+fx[i][j]*fy[i][j];
su[i][j]%=Mod;
}
}
for(int i=1;i<n-1;++i){
for(int j=1;j<=m;++j){
anc+=fx[i][j]*(sum[i+fy[i][j]][j]-sum[i+1][j]);
anc%=Mod;
anf+=fx[i][j]*(su[i+fy[i][j]][j]-su[i+1][j]);
anf%=Mod;
}
}
cout<<anc*c<<" "<<anf*f<<endl;
}
return 0;
}