【题解】P8865 [NOIP2022] 种花(二分答案,前缀和)

【题解】P8865 [NOIP2022] 种花

场外 VP 选手。唯一场切的一道题,写篇题解纪念一下。(

顺便提一嘴:e 我是真的菜,,其他人&题解这道题都是 \(O(nm)\) 的,就我是 \(O(nm\log n)\)。。~~无事凭空造 \(\log\) ~~~


题目链接

P8865 [NOIP2022] 种花

题意概述

有一个 \(n\times m\) 的网格图,要在网格图上种花。

有两种种花方案。

第一种是 \(\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-\) 的种花方案。

答案输出 \(\texttt C-\) 的方案数乘给定常数 \(c\)\(\texttt F-\) 的方案数乘给定常数 \(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\}\)

测试点编号 \(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\)

思路分析

注:我们用 \(a_{i,j}\) 表示网格图上第 \(i\) 行第 \(j\) 列的数。

对于这种问题,可以考虑从某一个角度开始来思考它。

首先以 \(\texttt C-\) 形为例:

我们可以分别枚举位置 \((x,y)\),然后考虑以 \((x,y)\)\(\texttt C-\) 形图案的左上角时,有多少种种花方案。

其实此时的种花方案数取决于三点:

  • \((x,y)\) 向右有多少个点种花了;
  • \((x,y)\) 向下走有多少个点 \((x,z)\) 能够成为 \(\texttt C-\) 形图案的左下角
  • \(\texttt C-\) 形图案的左下角向右有多少个点种花了;

我们定义 \(sum1_{i,j}\) 表示第 \(i\)\(a_{i,1}\)\(a_{i,j}\) 的前缀和。那么从 \((x,y)\) 开始,向右最远能种花的位置就是从 \((x,y)\) 往右走第一个 \(1\) 的位置,即第一个 \(sum1_{i,k}-sum2_{i,j}>0\) 的位置。发现这个东西是满足单调性的,那么我们可以二分求解。

我们将从 \((x,y)\) 开始向右最远能种花的位置记为 \(pos1_{x,y}\)

定义 \(sum2_{i,j}\) 表示第 \(i\)\(a_{1,i}\)\(a_{j,i}\) 的前缀和。那么从 \((x,y)\) 向下走,同理最远能够成为左下角的点就是第一个 \(sum2_{j,k}-sum2_{j,i}>0\) 的位置,这个东西同样满足单调性,也可以二分求解。将从 \((x,y)\) 开始向下走最远能成为左下角的点的位置记为 \(pos2_{x,y}\)

那么对于一个点 \((x,y)\),满足题意的方案数就是分别考虑,当这个点成为左上角时,所有能够成为这个 \(\texttt C-\) 形左下下角的点的方案数之和乘上 \(pos1_{x,y}-x\)

我们发现,对于每一个能成为左下角的点 \((x_0,y)\),它的方案数是 \(pos1_{x_0,y}-x_0\)

那么总的答案就是对于所有 \((x_0,y)\)\(x \le x_0\le pos2_{x,y}\) 的方案数求和。

可以用前缀和来预处理出来一个 \(sum_{i,j}\) 表示第 \(i\) 列从 \((j,1)\)\((j,i)\)\(pos1_{j,i}-i\) 之和。

那么我们就可以直接每次用 \(sum[j][pos2[i][j]]-sum[j][i+1]\) 就是能够成为这个 \(\texttt C-\) 形左下角的点的方案数之和。最后再给它乘上 \(pos1_{x,y}-x\) 即可。

对于 \(\texttt F-\),我们可以类比 \(\texttt C-\),即当一个点成为左上角时,所有能够成为 \(\texttt F-\) 形左下角的方案数之和是 \(suml[j][pos2[i][j]]-suml[j][i+1]\),其中 \(suml_{i,j}\) 表示的是第 \(i\) 列从 \((j,1)\)\((j,i)\)\((pos1_{j,i}-i)\times (pos2_{j,i}-j)\) 之和。

最后直接枚举每个 \(a_{i,j}= 0\) 的点作为左上角然后直接将所有方案数相加即可。

时间复杂度 \(O(nm \log n)\)

代码实现

//luoguP8865
//A
#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=1005;
int sum1[maxn][maxn],sum2[maxn][maxn],a[maxn][maxn],pos1[maxn][maxn],pos2[maxn][maxn];
int pos[maxn][maxn],cnt[maxn],sum[maxn][maxn],suml[maxn][maxn];
int n,m,c,f;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void Clear()
{
	memset(sum1,0,sizeof(sum1));
	memset(sum2,0,sizeof(sum2));
	memset(cnt,0,sizeof(cnt));
	memset(sum,0,sizeof(sum));
	memset(suml,0,sizeof(suml));
}

signed main()
{
	int T,id;
	T=read();id=read();
	while(T--)
	{
		n=read();m=read();c=read();f=read();
		Clear();
		for(int i=1;i<=n;i++)
		{
			string s;
			cin>>s;
			for(int j=1;j<=m;j++)
			{
				a[i][j]=s[j-1]-'0';
				sum1[i][j]=sum1[i][j-1]+a[i][j];
			}
		}
		for(int i=1;i<=m;i++)
		{
			for(int j=1;j<=n;j++)
			{
				sum2[i][j]=sum2[i][j-1]+a[j][i];
			}
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				if(a[i][j]==1){pos1[i][j]=j,pos2[i][j]=i;continue;}
				int now=j;
				for(int step=(1<<10);step>=1;step>>=1)
				{
					if(now+step<=m&&sum1[i][now+step]-sum1[i][j]==0)now+=step;
				}
				pos1[i][j]=now;
				now=i;
				for(int step=(1<<10);step>=1;step>>=1)
				{
					if(now+step<=n&&sum2[j][now+step]-sum2[j][i]==0)now+=step;
				}
				pos2[i][j]=now;
			}
		}
		for(int i=1;i<=m;i++)
		{
			cnt[i]=1;
			for(int j=1;j<=n;j++)
			{
				(sum[i][j]=sum[i][j-1]+pos1[j][i]-i)%=mod;
				(suml[i][j]=suml[i][j-1]+(pos2[j][i]-j)*(pos1[j][i]-i)%mod)%=mod;
			}
		}
		int ansc=0,ansf=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				if(pos2[i][j]>i+1)(ansc+=(pos1[i][j]-j)*((sum[j][pos2[i][j]]-sum[j][i+1]+mod)%mod)%mod)%=mod;
				if(pos2[i][j]>i+1)(ansf+=(pos1[i][j]-j)*((suml[j][pos2[i][j]]-suml[j][i+1]+mod)%mod)%mod)%=mod;
			}
		}
		cout<<ansc*c%mod<<" "<<ansf*f%mod<<'\n';
	}
}

虽说我的做法时间复杂度没传统正解优秀,而且思路上被别人说也挺大便的。。但毕竟是我自己想出来的,而且没有写挂一遍 AC,所以还是记录下来吧。

posted @ 2022-12-09 15:31  向日葵Reta  阅读(48)  评论(0编辑  收藏  举报