USACO-Section 3.3 Home on the Range (树状数组||DP)

此处有目录↑

描述

农民约翰在一片边长是N (2 <= N <= 250)英里的正方形牧场上放牧他的奶牛。(因为一些原因,他的奶牛只在正方形的牧场上吃草。)遗憾的是,他的奶牛已经毁坏一些土地。( 一些1平方英里的正方形)

农民约翰需要统计那些可以放牧奶牛的正方形牧场(至少是2x2的,在这些较大的正方形中没有一个点是被破坏的,也就是说,所有的点都是“1”)。

你的工作要在被供应的数据组里面统计所有不同的正方形放牧区域(>=2x2)的个数。当然,放牧区域可能是重叠。

格式

PROGRAM NAME: range

INPUT FORMAT:

(file range.in)

  • 第 1 行:N,牧区的边长。
  • 第 2 到 n+1 行:N个没有空格分开的字符。0 表示 "那一个区段被毁坏了";1 表示 " 准备好被吃"。

OUTPUT FORMAT:

(file range.out)

输出那些存在的正方形的边长和个数,一种一行。

SAMPLE INPUT

6
101111
001111
111111
001111
101101
111001

SAMPLE OUTPUT

2 10
3 4
4 1

解法一:树状数组

看见题的第一反应是二维树状数组,通过计算正方形区域中数字的和判断是否符合题意。

找出每一点作为正方形的右下角能形成的符合题意的正方形的最大边长,但直接枚举复杂度是O(n^3),所以枚举边长时通过二分的方法,使复杂度降至O(n^2*logn)

二分真的好容易写跪...

但是ans[i]并非真正的边长为i的正方形的个数,而是某一点能形成的最大边长为i的个数,所以还需要再处理才能是正解

/*
ID: your_id_here
PROG: range
LANG: C++
*/
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>

#define lowbit(x) (x&(-x))

using namespace std;

const int MAXN=255;

int a[MAXN][MAXN];
int ans[MAXN]={0},n;

void update(int x,int y) {
    int t=y;
    while(x<=n) {
        y=t;
        while(y<=n) {
            ++a[x][y];
            y+=lowbit(y);
        }
        x+=lowbit(x);
    }
}

int getSum(int x,int y) {
    int t=y,sum=0;
    while(x>0) {
        y=t;
        while(y>0) {
            sum+=a[x][y];
            y-=lowbit(y);
        }
        x-=lowbit(x);
    }
    return sum;
}

inline bool isSquare(int i,int j,int len) {
    return getSum(i,j)-getSum(i-len,j)-getSum(i,j-len)+getSum(i-len,j-len)==0;
}


int main() {
    freopen("range.in","r",stdin);
    freopen("range.out","w",stdout);

    int l,r,mid;
    char s[MAXN];
    scanf("%d",&n);
    for(int i=1;i<=n;++i) {
        scanf("%s",s+1);
        for(int j=1;j<=n;++j)//为了方便计算和判断,毁坏的区域用1表示
            if(s[j]=='0')
                update(i,j);
    }

    for(int i=2;i<=n;++i) {
        for(int j=2;j<=n;++j) {//以(i,j)为右下角的正方形
            l=2;
            r=min(i,j)+1;
            while(l<r) {//二分边长,找到第一个不符合的正方形
                mid=(l+r)>>1;
                if(isSquare(i,j,mid))
                    l=mid+1;
                else
                    r=mid;
            }
            if(isSquare(i,j,l-1))
                ++ans[l-1];//此时ans[i],以某一点为右下角最大能形成边长为i的正方形的个数
        }
    }
    for(int i=2;i<=n;++i)//由于ans[i]不是所有边长为i的个数,而最大能形成边长为i的正方形必定能形成相应个数的边长为1~i-1的正方形
        if(ans[i]>0)
            for(int j=2;j<i;++j)
                ans[j]+=ans[i];
    for(int i=2;i<=n;++i)
        if(ans[i]>0)
            printf("%d %d\n",i,ans[i]);
    return 0;
}

解法二:DP

看到有DP解法时,才想起以前见过这道题,而且也知道解法,还是忘性太大...

dp[i][j]表示以(i,j)为右下角的正方形的最大边长

/*
ID: your_id_here
PROG: range
LANG: C++
*/
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXN=255;

int dp[MAXN][MAXN];
int ans[MAXN]={0},n;

int main() {
    freopen("range.in","r",stdin);
    freopen("range.out","w",stdout);

    char s[MAXN];
    scanf("%d",&n);
    for(int i=1;i<=n;++i) {
        scanf("%s",s+1);
        for(int j=1;j<=n;++j)
            dp[i][j]=s[j]-'0';
    }
    for(int i=2;i<=n;++i) {
        for(int j=2;j<=n;++j) {
            if(dp[i][j]>0) {//只有当前点未损坏时,状态转移才有意义
                dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]))+1;
                ++ans[dp[i][j]];
            }
        }
    }

    for(int i=2;i<=n;++i)//由于ans[i]不是所有边长为i的个数,而最大能形成边长为i的正方形必定能形成相应个数的边长为1~i-1的正方形
        if(ans[i]>0)
            for(int j=2;j<i;++j)
                ans[j]+=ans[i];
    for(int i=2;i<=n;++i)
        if(ans[i]>0)
            printf("%d %d\n",i,ans[i]);
    return 0;
}


你可能感兴趣的:(dp,USACO,树状数组)