1. 程式人生 > 實用技巧 >woj1012 Thingk and Count DP好題

woj1012 Thingk and Count DP好題


title: woj1012 Thingk and Count DP好題
date: 2020-03-12
categories: acm
tags: [acm,dp,woj]

難題,dp好題,幾何題(?數學題)
想明白了其實思路很簡單,但是因為資料量很大優化需要考慮.
滾動陣列。

description

You are given a chessboard made up of N squares by M squares. Some of the squares are colored black, and the others are colored white. Please
write a program to calculate the number of rectangles which are completely made up of white squares.

輸入格式

There are multiple test cases. Each test case begins with two integer N,M (1 <= N , M<= 2000), the board size. The following N lines, each with M
characters, have only two valid character values:
b - representing a black square;
w - representing a white square.
Process to the end of file.

輸出格式

For each test case in the input, output the number of white rectangles a line.

樣例輸入

2 3
bbb
www
2 2
bw
wb

樣例輸出

6
2

analyse

dp經典。
f(i,j)為以點[i][j]為右下角的矩形數量,這裡化為滾動陣列sum。

經驗教訓:
這種看著像dp 的可以試一試dp,然後找遞推公式的時候注意,新增的i,和原來整體對比看有什麼關係或者和i-1有什麼關係

幾何體畫圖,多畫幾種情況,考慮全
資料量很大的時候,用滾動陣列

然後這種方塊題通解就是計算以[i,j]為底的矩形數累加
然後考慮資料量用long long,輸出%lld

hud1510//zoj2067
ZJU的1985也可以看一看

code final

//zoj2067改woj1012,再改良版
//zoj2067改woj1012見下面的註釋
#include <cstdio>
#include <cstring>
#include<iostream>
using namespace std;
int n,m;

const int maxn=2005;

char g[maxn];
int f[maxn]; //當前行x,i列上[x,i]為底的矩形高度
long long ans;  //ans很大,考慮溢位
int pre[maxn];
long long sum[maxn]; //sum[i]記錄以[x,i]為右下角的所有矩形數量,這樣[x,i+1]如果f[i+1]>f[i]直接查表就行
/*pre[i]為最近的比i列矩形低的矩形的距離 比如 
  a   a
a a a a  pre[2]=1,pre[3]=1.pre[i]=0表示i列是0-i中最矮列,比如pre[0]=0;
*/
int main(int argc, char *argv[])
{
    while( scanf("%d %d",&n,&m)!=EOF ){
        getchar();                       //注意,沒有可不可以?
        ans=0;
        for(int i=0;i<m;i++)
            f[i]=0;
        for(int i=0; i<n; i++){  //按行建圖並在建立完一行後就計算以該行上的方塊為右下角的矩形數量
           scanf("%s",g);
            for(int j=0;j<m;j++)
                if(g[j]=='b')
                    f[j]=0;
                else
                    f[j]++;
            sum[0] = 0 ;
            pre[0]=0;  //初始時沒有比0列更矮(或相等)的列,距離為0
            for(int j=0;j<m;j++){
                if(j==0)
                    sum[j]+=f[j];
                else if(f[j]>=f[j-1]){
                    sum[j]=f[j]+sum[j-1];
                    pre[j]=1;
                }
                else if(f[j]<f[j-1]){
                    pre[j]=pre[j-1]+1;
                    int temp=j-pre[j];
                    while(f[temp]>f[j]&&pre[temp]){  //當templie比j列高並且temp列是0-temp最矮列的時候,就沒必要繼續找了
                        temp=temp-pre[temp];
                    }

                    //考慮比j列矮的列就可以,不必考慮最矮列,比如高度為 1232342(0-6列),sum[6]=sum[3]+f[6]*(6-3),因為3之前的就算有更矮的,也已經被sum[3]計算時考慮過了
                    if(f[temp]>f[j]) //找不到比j列矮的列
                    {
                        pre[j]=0;
                        sum[j]=f[j]*(j+1);
                    }
                    else{ //找到比j列矮的列temp
                        pre[j]=j-temp;
                        sum[j]=f[j]*(j-temp)+sum[temp];
                    }
                }
            //cout<<j<<" "<<sum[j]<<"// ";  除錯的時候用
            ans+=sum[j];
            }
            //cout<<endl;
        }
        printf("%d\n",ans); //這裡ans考慮完了,一開始用的還是%d,一直WA,所以注意啊
    }    
    return 0;
}

code α

zoj2067改編到woj1012,超時。如果這個程式碼裡改成m=n,判斷改成=='#',zoj2067可AC
狀態:f(i,j)為記錄i行j列,[i][j]為底的符合要求的正方形連續組成的矩形的高度
以[i][j]為右下角的矩形初值為f(i,j)
然後在i行從j-1 到 0 遍歷矩形高度,不為0就可以擴充套件成以[i][j]為右下角的矩形
計算每個以每個點為右下角的矩形數量求和即為解

比如
a a
a a a a
a a a b ,算以b為右下角的矩形的時候先算了第三列,ans+3,然後前一列高度為3=3,ans+3,然後ans+2,ans+2...

然後看到網上很多的滾動陣列版,節省了時間(節省時間不是滾動陣列,是滾動陣列的意義和計算方法),感覺不太清楚,就按照一個思路寫了上面的程式碼

//zoj2067 ac
//結果還是在woj1012 te了。
#include <cstdio>
#include <cstring>
#include<iostream>
using namespace std;
int n,m;

const int maxn=2005;

char g[maxn][maxn];
int f[maxn][maxn];
int ans,tmp;

int main(int argc, char *argv[])
{
    while( scanf("%d %d",&n,&m)!=EOF ){
        getchar();
        ans=0;
        for(int i=0; i<n; i++){  //建圖
           scanf("%s",g[i]);
            for(int j=0;j<m;j++){
                if(g[i][j]=='b')  //這裡也可以優化。只用一個數組表示f[i][j]就行,因為一旦該點為0就和上面沒關係了,而不為0直接++就行
                    f[i][j]=0;
                else{
                    if(i==0)
                        f[i][j]=1;
                    else
                        f[i][j]=f[i-1][j]+1;
                    ans+=f[i][j];
                    tmp=f[i][j];
                    for(int k=j-1;k>=0;k--){   //然後這裡可以優化,記錄比前一個自己所在矩形高度低的列就行,沒必要每一列都迴圈
                        if(f[i][k]==0)
                            break;
                        else{
                            if(tmp>f[i][k])
                                tmp=f[i][k];
                            ans+=tmp;
                        }
                    }
                }
            }
        }
        printf("%d\n",ans);
    }    
    return 0;
}
網上比較多的AC程式碼,思路和最上面我的程式碼一樣,但是我覺得p,pre不太容易理解
#include<iostream>
#include<cstdio>

using namespace std;

int main(){
    char in[2001];
    long sign[2001], i, min, sum[2001], pre[2001], j, k, n, m, s, p;
    long long out;
    while (scanf("%ld %ld\n", &n, &m) != EOF)
    {
        for (i = 1; i <= m; i++) //每一列當前連續白色方塊數為0
            sign[i] = 0;
        for (s = 1, out = 0; s <= n; s++)
        {
            scanf("%s",in);  //因為按行處理,保留一行就可以  gets(in)也可以,和%s一樣略過了最後的\n
            for (i = 0; i < m; i++)//記錄i列s行,[s][i]為底的符合要求的正方形連續組成的矩形的高度,滾動陣列1,處理單列上的白方塊總數
            {
                if (in[i] == 'w')  //注意這裡是sign[i+1],為了下面的處理方便
                    sign[i + 1]++;
                else
                    sign[i + 1] = 0;
            }

            //然後橫向處理計算以[s][i]為右下角的矩形的數量
            for (i = 1, sign[0] = pre[0] = 0; i <= m; i++) //0列是虛構的一列。注意輸入是從in[0]開始的,為了方便處理i從1開始
            {
                if (sign[i] >= sign[i - 1])//如果比前列的矩形高,以[s][i-1]為右下角的所有的矩形都可以擴充套件到以[s][i]為右下角的矩形,同時i列以[s][i]為右下角的矩形數量為sign[i]
                {
                    sum[i] = sum[i - 1] + sign[i];//滾動陣列2,記錄前i列的白方塊總數
                    pre[i] = 0;   //pre[i] 比i列矩形高度低的列的距離-1
                }
                else
                {
                    pre[i] = pre[i - 1] + 1;
                    p = i - pre[i];
                    while (p > 0 && sign[p - 1] >= sign[i])//找出比這列sign[]小的列
                        p = p - pre[p - 1] - 1;
                    if (p <= 0)//這列sign[]值最小,h*w計算[s][i]為右下角的矩形就可以
                    {
                        sum[i] = sign[i] * i;
                        pre[i] = 0;
                    }
 
                    else//p列sign[]值最小,分兩部分,p列前的可以擴充套件到[s][i]為右下角的矩形,而p列到i列可以按h*w算
                    {
                        sum[i] = sign[i] * (i - p + 1) + sum[p - 1];
                        pre[i] = i - p - 1;
                    }
                }
                out += sum[i]; //每列加一次,加新生成的矩形數
            }
        }
        printf("%lld\n", out);
    }
    return 0;

}