【題解】P8865 [NOIP2022] 種花(二分答案,字首和)
【題解】P8865 [NOIP2022] 種花
場外 VP 選手。唯一場切的一道題,寫篇題解紀念一下。(
順便提一嘴:e 我是真的菜,,其他人&題解這道題都是 \(O(nm)\) 的,就我是 \(O(nm\log n)\)。。~~無事憑空造 \(\log\) ~~~
題目連結
題意概述
有一個 \(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\)
第二種是 \(\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\)
求給定網格圖有多少種 \(\texttt C-\) 和 \(\texttt F-\) 的種花方案。
答案輸出 \(\texttt C-\) 的方案數乘給定常數 \(c\) 和 \(\texttt F-\) 的方案數乘給定常數 \(f\) 對 \(998244353\) 取模的結果即可。
資料範圍
對於所有資料,保證:\(1 \leq T \leq 5\)
測試點編號 | \(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,所以還是記錄下來吧。