hdu 4331 Image Recognition(暴力优化&正解)

来源:互联网 发布:手机古琴软件 编辑:程序博客网 时间:2024/04/29 11:15

Image Recognition

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 899    Accepted Submission(s): 336


Problem Description
Now there is an image recognition problem for you. Now you are given an image which is a N * N matrix and there are only 0s and 1s in the matrix. And we are interested in the squares in whose four edges there is no 0s. So it’s your task to find how many such squares in the image.
 

Input
The first line of the input contains an integer T (1<=T<=10) which means the number of test cases.
For each test cases, the first line is one integer N (1<=N<=1000) which is the size of the image. Then there are N lines and each line has N integers each of which is either 0 or 1.
 

Output
For each test case, please output a line which is "Case X: Y", X means the number of the test case and Y means the number of the squares we are interested in in the image.
 

Sample Input
131 1 01 1 00 0 0
 

Sample Output
Case 1: 5
 

Source
2012 Multi-University Training Contest 4
 

Recommend
zhoujiaqi2010

题意:
    本题题目大意在一个01方阵中找出四条边全都是1的正方形的个数,对于正方形内部则没有要求。
     这题用暴力居然过了只用了2s,不过如果出到极端数据是过不了的。比如1000*1000个1的话程序要跑4s。
思路:
方法很简单。对于每一个点维护四个数据。up,lef,ri,dw.分别记录包括自己上边,左边,右边,下边有多少个连续的1。因为题目要求找出四条边全都是1的正方形的个数。这些数据统计好后。再每个点用lu记录up和lef的最小值。因为如果该点为正方形的顶点的话。边长最多依照边长lef和up小的来。(要保证边上都是1只能按1少的边来)同理每个点用rd记录ri和dw的最小值。然后枚举正方形的左上角。然后枚举正方形的边长从1到rd。如果右下角也满足条件(lu>枚举的边长)则答案加1。
#include <iostream>#include<string.h>#include<stdio.h>#include<time.h>using namespace std;const int maxn=1010;int maze[maxn][maxn],up[maxn][maxn],lef[maxn][maxn];//记录地图,上面和左边连续1的个数int dw[maxn][maxn],ri[maxn][maxn];//记录上边和右边连续1的个数。int lu[maxn][maxn],rd[maxn][maxn];//记录左上角和右下角最小的连续1的个数int n;//clock_t start, finish;//double  duration;int main(){    int t,cas,i,j,k,nx,ny,ans;    //start=clock();    //freopen("in.txt","r",stdin);    //freopen("in.txt","w",stdout);    scanf("%d",&t);    cas=1;    while(t--)    {        scanf("%d",&n);        memset(lef,0,sizeof(lef));        memset(up,0,sizeof(up));        memset(dw,0,sizeof(dw));        memset(ri,0,sizeof(ri));        ans=0;        for(i=1; i<=n; i++)        {            for(j=1; j<=n; j++)//统计            {                scanf("%d",&maze[i][j]);                if(maze[i][j])                {                    lef[i][j]=lef[i][j-1]+1;                    up[i][j]=up[i-1][j]+1;                }                lu[i][j]=min(up[i][j],lef[i][j]);            }        }        for(i=n; i>0; i--)        {            for(j=n; j>0; j--)            {                if(maze[i][j])                {                    dw[i][j]=dw[i+1][j]+1;                    ri[i][j]=ri[i][j+1]+1;                }                rd[i][j]=min(ri[i][j],dw[i][j]);            }        }        for(i=1; i<=n; i++)        {            for(j=1; j<=n; j++)            {                if(maze[i][j])                {                    for(k=1; k<=rd[i][j]; k++)//枚举边长                    {                        nx=i+k-1;                        ny=j+k-1;                        if(lu[nx][ny]>=k)                            ans++;                    }                }            }        }        printf("Case %d: %d\n",cas++,ans);        //finish=clock();        //duration = (double)(finish - start) / CLOCKS_PER_SEC;        //printf("%lf\n",duration);    }    return 0;}

   经过一番学习后。掌握了O(n^2logn)的算法。
思路:
一个直观的想法是首先用N^2的时间预处理出每一个是1的点向上下左右四个方向能够延伸的1的最大长度,记为四个数组l, r, u, d。然后我们观察到正方形有一个特征是同一对角线上的两个顶点在原方阵的同一条对角线上。于是我们可以想到枚举原来方阵的每条对角线,然后我们对于每条对角线枚举对角线上所有是1的点i(以列为准,因为在同一对角线上以一个坐标判断就够了),那么我们可以发现可能和i构成正方形的点应该在该对角线的 [i, i + min(r[i], d[i]) – 1] 闭区间内, 而在这个区间内的点 j 只要满足 j – i + 1 <= min(l[j], u[j]) 也就是满足j – min(l[j], u[j]) + 1 <= i,这样的 (i, j) 就能构成一个正方形。也就是说对于每条对角线,我们可以构造一个数组 a, 使得a[i] = i – min(l[i], u[i]) + 1然后对这个数组有若干次查询,每次查询的是区间 [i, i + min(r[i], d[i]) – 1]内有多少个数满足 a[j] <= i,所有这些问题答案的和就是该问题的结果。对于这个问题,我们可以通过离线算法,先保存所有查询的区间端点,并对所有端点排序。然后使用扫描线算法,如果扫描到的是第i次查询的左端点,就让当前结果减去当前扫描过的数中 a[i]<= i的个数,如果扫描到的是第i次查询的右端点点,则让当前结果加上当前扫描过的数中a[i] <= i的个数,最后所有结果相加即可。维护当前数出现的个数可以使用树状数组。这样对于每条对角线求结果的复杂度为O(nlogn),算法总的复杂度为O(n^2logn)。

#include <iostream>#include<string.h>#include<stdio.h>#include<time.h>#include<algorithm>using namespace std;const int maxn=1010;struct node{    int pos,id;//存区间的端点和序号    bool left;//存是否为左端点} endp[2*maxn];int maze[maxn][maxn],c[maxn],a[maxn];//存地图。int u[maxn][maxn],d[maxn][maxn],l[maxn][maxn],r[maxn][maxn];int n,ans,cnt;//clock_t start, finish;//double  duration;int lowbit(int x){    return x&(-x);}void update(int x){    while(x<=n)    {        c[x]++;        x+=lowbit(x);    }}int getsum(int x){    int sum=0;    while(x>0)    {        sum+=c[x];        x-=lowbit(x);    }    return sum;}bool cmp(node a,node b){    if(a.pos==b.pos)        return a.left;//左端点在前    return a.pos<b.pos;//满足这样的条件a才在b前面}void init(){    memset(l,0,sizeof l);    memset(u,0,sizeof u);    memset(d,0,sizeof d);    memset(r,0,sizeof r);    ans=0;}void cans(){    int i;    memset(c,0,sizeof c);    sort(endp,endp+cnt,cmp);    for(i=0; i<cnt; i++)    {        if(endp[i].left)        {            ans-=getsum(endp[i].pos);//减去当前扫描过的数中 <= i的个数            update(a[endp[i].pos]);//插入a[i]        }        else            ans+=getsum(endp[i].id);//结果加上当前扫描过的数中 a[i]<= i的个数    }}void addin(int x,int y)//把区间加到数组中{    a[y]=y-min(u[x][y],l[x][y])+1;    endp[cnt].id=endp[cnt].pos=y;    endp[cnt].left=true;    cnt++;    endp[cnt].id=y;    endp[cnt].pos=y+min(r[x][y],d[x][y])-1;    endp[cnt].left=false;    cnt++;}void solve(){    int i,j,x,y;    for(i=n; i>0; i--)//枚举对角线\。从左下对角线和主对角线    {        cnt=0;        for(j=1; j<=n-i+1; j++)        {            x=i+j-1;            y=j;            if(maze[x][y])                addin(x,y);        }        cans();    }    for(i=2; i<=n; i++)//右上边的对角线    {        cnt=0;        for(j=1; j<=n-i+1; j++)        {            y=i+j-1;            x=j;            if(maze[x][y])                addin(x,y);        }        cans();    }}int main(){    int t,cas,i,j;    //start=clock();    //freopen("in.txt","r",stdin);    //freopen("in.txt","w",stdout);    scanf("%d",&t);    cas=1;    while(t--)    {        scanf("%d",&n);        init();        for(i=1; i<=n; i++)        {            for(j=1; j<=n; j++)//统计            {                scanf("%d",&maze[i][j]);                if(maze[i][j])                {                    l[i][j]=l[i][j-1]+1;                    u[i][j]=u[i-1][j]+1;                }            }        }        for(i=n; i>0; i--)        {            for(j=n; j>0; j--)            {                if(maze[i][j])                {                    d[i][j]=d[i+1][j]+1;                    r[i][j]=r[i][j+1]+1;                }            }        }        solve();        printf("Case %d: %d\n",cas++,ans);        //finish=clock();        //duration = (double)(finish - start) / CLOCKS_PER_SEC;        //printf("%lf\n",duration);    }    return 0;}


原创粉丝点击