1. 程式人生 > 實用技巧 >題解 CF1200D 【White Lines】

題解 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\)

,往\(i,j+1\)繼續列舉時,我們只用轉移一列,如圖。

此時只有兩邊的答案更新了,我們只需要考慮如何更新這兩列的答案即可。

考慮對每一列做一個字首和統計黑點的個數,每次從答案中減去左邊那列的貢獻,加上右邊的貢獻就可以了(貢獻就是如果這段的黑點個數等於這列的黑點個數,答案就加一)。

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\)

次,但是我們的查詢操作需要每個位置進行一次,即\((n-k)^2\)次。所以我們可以直接用一個數組暴力打標記,這樣是\(O(n)\)的,查詢查一個點,是\(O(1)\)的,可以通過。

總時間複雜度除開讀入外是\(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;
}

圖是不是有點醜