[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\) 取模的结果即可,具体输出结果请看输出格式部分。

输入格式

第一行包含两个整数 \(T, id\),分别表示数据组数和测试点编号。如果数据为样例则保证 \(id = 0\)。

接下来一共 \(T\) 组数据,在每组数据中:

第一行包含四个整数 \(n, m, c, f\),其中 \(n, m\) 分别表示花园的行数、列数,对于 \(c, f\) 的含义见输出格式部分。

接下来 \(n\) 行,每行包含一个长度为 \(m\) 且仅包含 \(0\) 和 \(1\) 的字符串,其中第 \(i\) 个串的第 \(j\) 个字符表示 \(a_{i,j}\),即花园里的第 \(i\) 行第 \(j\) 列是不是一个土坑。

输出格式

设花园中 \(\texttt{C-}\) 形和 \(\texttt{F-}\) 形的种花方案分别有 \(V_C\) 和 \(V_F\) 种,则你需要对每一组数据输出一行用一个空格隔开的两个非负整数,分别表示 \((c \times V_C) \bmod 998244353\),\((f \times V_F ) \bmod 998244353\) ,其中 \(a \bmod P\) 表示 \(a\) 对 \(P\) 取模后的结果。

样例 #1

样例输入 #1

1 0
4 3 1 1
001
010
000
000

样例输出 #1

4 2

提示

【样例 1 解释】

四个 \(\texttt{C-}\) 形种花方案为:

**1 **1 **1 **1
*10 *10 *10 *10
**0 *** *00 *00
000 000 **0 ***

其中 \(\texttt*\) 表示在这个位置种花。注意 \(\texttt C\) 的两横可以不一样长。

类似的,两个 \(\texttt{F-}\) 形种花方案为:

**1 **1
*10 *10
**0 ***
*00 *00

【样例 2】

见附件下的 \(\texttt{plant/plant2.in}\) 与 \(\texttt{plant/plant2.ans}\)。

【样例 3】

见附件下的 \(\texttt{plant/plant3.in}\) 与 \(\texttt{plant/plant3.ans}\)。

【数据范围】

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

测试点编号 \(n\) \(m\) \(c=\) \(f=\) 特殊性质 测试点分值
\(1\) \(\leq 1000\) \(\leq 1000\) \(0\) \(0\) 无 \(1\)
\(2\) \(=3\) \(=2\) \(1\) \(1\) 无 \(2\)
\(3\) \(=4\) \(=2\) \(1\) \(1\) 无 \(3\)
\(4\) \(\leq 1000\) \(=2\) \(1\) \(1\) 无 \(4\)
\(5\) \(\leq 1000\) \(\leq 1000\) \(1\) \(1\) A \(4\)
\(6\) \(\leq 1000\) \(\leq 1000\) \(1\) \(1\) B \(6\)
\(7\) \(\leq 10\) \(\leq 10\) \(1\) \(1\) 无 \(10\)
\(8\) \(\leq 20\) \(\leq 20\) \(1\) \(1\) 无 \(6\)
\(9\) \(\leq 30\) \(\leq 30\) \(1\) \(1\) 无 \(6\)
\(10\) \(\leq 50\) \(\leq 50\) \(1\) \(1\) 无 \(8\)
\(11\) \(\leq 100\) \(\leq 100\) \(1\) \(1\) 无 \(10\)
\(12\) \(\leq 200\) \(\leq 200\) \(1\) \(1\) 无 \(6\)
\(13\) \(\leq 300\) \(\leq 300\) \(1\) \(1\) 无 \(6\)
\(14\) \(\leq 500\) \(\leq 500\) \(1\) \(1\) 无 \(8\)
\(15\) \(\leq 1000\) \(\leq 1000\) \(1\) \(0\) 无 \(6\)
\(16\) \(\leq 1000\) \(\leq 1000\) \(1\) \(1\) 无 \(14\)

特殊性质 A:\(\forall 1 \leq i \leq n, 1 \leq j \leq \left\lfloor \frac{m}{3} \right\rfloor\),\(a_{i, 3 j} = 1\);

特殊性质 B:\(\forall 1 \leq i \leq \left\lfloor \frac{n}{4} \right\rfloor, 1 \leq j \leq m\),\(a_{4 i, j} = 1\);

题解

这个题是独立做出来的绿题,非常开心
我们来看C的方案,观察样例就知道,对于C的两个顶点,我们需要知道每个顶点右面有几个零,如果知道这点,我们只需要使用乘法原理即可
对于(i,j)这个格子,我们怎么统计他右面有几个零?
刚开始我的方法是暴力维护,暴力维护的时间复杂度太高,我一直T的原因就在这里,后面我进行了修改,倒序枚举,从第m列开始,如果这个格子的数字是0,那么xh[i][j]=xh[i][j+1]+1,否则xh[i][j]=0,当然,我们也需要统计向上有多少个?方法是同样的。

那么我们怎么找C的方案数?我们发现在竖的方向上,至少存在3个连续的0,同时他们右面有多个0,我们是可以有C方案的,可以暴力统计,但是我发现,这个可以递推,用lst[i][j]记录在连续的0里面,目前匹配的数量是多少?如果当然(i,j)连续0的数量符合条件,那么前lst[i-2][j]是符合条件的,也就是说,和(i-2,j)匹配的,一定和他匹配,同时需要加上(i-2,j) 的向右数量,否则,匹配数量为0,同时更新lst[i][j],如何更新lst[i][j]呢?lst[i][j]默认等于lst[i-1][j],如果他自己也能匹配,加上自己向右的数量即可

经过上述过程,记录到pp[i][j]表示前面有多少个匹配数量,直接和0的数量相乘即可,那么C的问题就解决了

C的问题解决了之后,F的问题也就好解决了,因为F正好是C加上1个0,这个想法也是我偶然想到的,不得不说,这题出的实在是妙,我们记录C方案的前缀和即可

反思

对于我来说,最难的部分是对于(i,j)找匹配的问题,考虑到会出现如下的情况
0000
01
01
00000
那么我记录了一下上次的匹配好的位置,如果这次能匹配,那么递推一下,把上次匹配的数量加上他右面的连续0的数量,如果上次的位置和i的距离超过2,那么按照上述的方法来实现,如果遇到这种情况,上次匹配的位置是上一个,那么把前一个的匹配直接加上(i-2,j)的向右的数量
00000
0000
000
00
同时,我如何记录上一个位置呢?以列尾单位,用lst[j]记录了前一个位置,如果等于1,那么lst[j]归零,

点击查看代码
lst[1]=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(s[i][j]=='0'){
                    if(xs[i][j]>=3&&xh[i][j]>=2){
                        if(i-lst[j]>=2) pp[i][j]=pp[lst[j]][j]+xh[lst[j]][j]-1;
                        else {
                            int d=lst[j];
                            pp[i][j]=pp[lst[j]][j];
                            if(d<1) continue;
                            if(xh[d-1][j]>=2) pp[i][j]+=xh[d-1][j]-1;
                        }
                    }
                    if(xh[i][j]>=2) lst[j]=i;
                }else{
                    lst[j]=0;
                }
                
            }
        }
这样的实现方法是错误的,为什么?例如下面的例子 00000 0000 01 0000 lst是第2行,匹配数量等于2行的匹配,但是第1行也可以匹配,算法做不到 0000 所以这么写是错误的 ## 代码实现
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int n,m,c,ff,id,t;
char s[maxn][maxn];
int xh[maxn][maxn],f[maxn][maxn],xs[maxn][maxn],pp[maxn][maxn],lst[maxn][maxn],sum[maxn][maxn];
long long ansc,ansf;
int main(){
    //freopen("plant4.in","r",stdin);
    scanf("%d%d",&t,&id);
    for(int ss=1;ss<=t;ss++){
        scanf("%d%d%d%d",&n,&m,&c,&ff);
        memset(xh,0,sizeof xh);
        memset(xs,0,sizeof xs);
        memset(f,0,sizeof f);
        memset(lst,0,sizeof lst);
        memset(pp,0,sizeof pp);
        memset(sum,0,sizeof sum);
        ansc=ansf=0;
        for(int i=1;i<=n;i++){
            scanf("%s",s[i]+1);
            if(s[i][m]=='0') xh[i][m]=1;
            else xh[i][m]=0;
            for(int j=m-1;j>=1;j--)
                if(s[i][j]=='0'){
                    xh[i][j]=xh[i][j+1]+1;
                    xs[i][j]=xs[i-1][j]+1;
                }
                else{
                    xh[i][j]=0;
                    xs[i][j]=0;
                }
        }
//        for(int i=1;i<=n;i++){
//            for(int j=1;j<=m;j++)
//                cout<<xs[i][j]<<" ";
//            cout<<endl;
//        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(s[i][j]=='0'){
                    if(xs[i][j]>=3){
                        if(xh[i][j]>=2) pp[i][j]=lst[i-2][j];
                        else pp[i][j]=0;
                    }
                    if(xh[i][j]>=2) lst[i][j]=lst[i-1][j]+xh[i][j]-1;
                    else lst[i][j]=lst[i-1][j];
                }else{
                    lst[i][j]=0;
                }
                
            }
        }
        for(int i=3;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(s[i][j]=='0'&&xh[i][j]>=2&&xs[i][j]>=3){
                    f[i][j]=(xh[i][j]-1)*pp[i][j]%998244353;
                }
            }
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                if(s[i][j]=='0') {
                    ansf=(ansf+sum[i-1][j])%998244353;
                    sum[i][j]=(sum[i-1][j]+f[i][j])%998244353;
                    
                }
                else sum[i][j]=0;
            }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                ansc=(ansc+f[i][j])%998244353;
        
        ansc=ansc*c%998244353;
        ansf=ansf*ff%998244353;
        printf("%lld %lld\n",ansc,ansf);
    }
}
/**
1 7
10 10 1 1
0000000000
0000000100
0000100000
0000000000
0000000000
0000000000
0000000001
0000000000
0010000001
0000000000
 */
posted @ 2024-01-24 11:38  xinyimama  阅读(171)  评论(0)    收藏  举报