1. 程式人生 > 其它 >【題解】P8865 [NOIP2022] 種花(二分答案,字首和)

【題解】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,所以還是記錄下來吧。