題解 CF1200D 【White Lines】
突然發現自己的做法很清奇,於是就來寫一篇題解了。
覺得自己還是可以,居然沒用二維的東西維護答案
題意是在一個黑白矩陣上選一個小矩形染色為白,讓全白的行列最多。
Part.1
考慮一種最暴力的做法,我們列舉小矩陣的左上角,暴力染色後統計行列的數量。
這個演算法的時間複雜度為\(O((n-k)^2*k)\),可以通過\(n,k<=400\)或\(n,k\)值接近的點。
但這個演算法在\(k=\frac{n}{2}\)時複雜度會被卡成\(O(n^3)\)。但如果出題人資料水可能可以比正解跑的快
Part.2
先來考慮一列的答案。
如果這一列全是白色,無論在哪裡時它都有貢獻,先預處理出來。
考慮我們在每次列舉到\(i,j\)
此時只有兩邊的答案更新了,我們只需要考慮如何更新這兩列的答案即可。
考慮對每一列做一個字首和統計黑點的個數,每次從答案中減去左邊那列的貢獻,加上右邊的貢獻就可以了(貢獻就是如果這段的黑點個數等於這列的黑點個數,答案就加一)。
Part.3
我們再來考慮如何統計一行的貢獻。
如果這一行全是白色,同理的,先預處理出來。
同樣的,我們在從\(i\)往\(i+1\)列舉時,也只用更新一行的答案。
考慮如何統計一行的貢獻,先只看一行,我們列舉到\(i\)時包含了\([i,i+k-1]\)這個區間。
我們考慮只有這個區間包含了所有黑點時才對答案有貢獻,所以我們只用記錄下最左邊的黑點與最右邊的黑點。
如圖,只有在包含了整個區間時才有貢獻,如果它的區間大小沒有整個區間大,那這行就永遠沒有貢獻。
所以拓展到\(k\)行,我們只需要計算列舉到\(i\)時每行的一個區間時對答案是否有貢獻即可。
但這裡是不是要用到資料結構呢,實際上不用,我們這裡的問題比這個要弱的多。
顯然\([i,i+k-1]\)這個區間它只有它的左端點決定,我們可以只統計這個區間的左端點在哪個區間合法。
設左黑點的位置是\(L\),右黑點的位置是\(R\),那麼整個區間能被全覆蓋的充要條件是\(R-k+1\leq i\leq L\),那麼我們只需要對\([R-k+1,L]\)區間打標記即可。
這裡這個步驟的的實現不需要用任何資料結構,考慮我們只用列舉的每一行進行一次這樣的操作,需要\(n-k\)
總時間複雜度除開讀入外是\(O((n-k)\times n)\)的,可以通過。
\(Code\)
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn=2005;
char s[maxn][maxn];
int sum[maxn][maxn];
int vis[maxn],n,k;
int L[maxn],R[maxn],hans,lans,ans,llans;
inline int max(const int &x,const int &y)
{
return x>y?x:y;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)
scanf("%s",s[i]+1);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(s[i][j]=='B') {L[i]=j;break;}
for(int i=1;i<=n;++i)
for(int j=n;j>0;--j)
if(s[i][j]=='B') {R[i]=j;break;}
for(int j=1;j<=n;++j)
{
for(int i=1;i<=n;++i)
{
sum[i][j]=sum[i-1][j];
if(s[i][j]=='B') sum[i][j]++;
}
if(sum[n][j]==0) llans++;
}
for(int i=1;i<=n;++i)
if(L[i]==0&&R[i]==0) hans++;
for(int i=1;i<=k;++i)
{
if(R[i]-L[i]+1>k) continue;
for(int j=max(1,R[i]-k+1);j<=L[i];++j)
++vis[j];
}
for(int i=1;i<=n-k+1;++i)
{
if(i!=1)
{
for(int j=max(1,R[i-1]-k+1);j<=L[i-1];++j)
--vis[j];
for(int j=max(1,R[i+k-1]-k+1);j<=L[i+k-1];++j)
++vis[j];
}
lans=llans;
for(int j=1;j<=k;++j)
if(sum[n][j]&&sum[i+k-1][j]-sum[i-1][j]==sum[n][j]) lans++;
ans=max(ans,lans+vis[1]);
for(int j=2;j<=n-k+1;++j)
{
if(sum[n][j-1]&&sum[i+k-1][j-1]-sum[i-1][j-1]==sum[n][j-1]) lans--;
if(sum[n][j+k-1]&&sum[i+k-1][j+k-1]-sum[i-1][j+k-1]==sum[n][j+k-1]) lans++;
ans=max(ans,lans+vis[j]);
}
}
printf("%d\n",ans+hans);
return 0;
}
圖是不是有點醜