ZJOI 2009 对称的正方形 RMQ+Manacher

题目链接点我点我:-)

题目描述
Orez很喜欢搜集一些神秘的数据,并经常把它们排成一个矩阵进行研究。最近,Orez又得到了一些数据,并已经把它们排成了一个n行m列的矩阵。通过观察,Orez发现这些数据蕴涵了一个奇特的数,就是矩阵中上下对称且左右对称的正方形子矩阵的个数。 Orez自然很想知道这个数是多少,可是矩阵太大,无法去数。只能请你编个程序来计算出这个数。
输入输出格式

输入格式
文件的第一行为两个整数n和m(n,m <= 1000)。接下来n行每行包含m个正整数,表示Orez得到的矩阵。

输出格式
文件中仅包含一个整数answer,表示矩阵中有answer个上下左右对称的正方形子矩阵。

思路
1. 把矩阵变成(2n-1)*(2m-1)的,即在数字中间补上0
2.(Manacher算法)定义lx[][], ly[][]数组分别表示对于每个矩阵中的数,它的左右(上下)的最长延伸回文串的长度
3.定义left[][]数组表示对于每个矩阵中的数,以它为中心,最多可以向左边延伸的半个矩形(保证上下回文)的长度(长度应小于所有在范围内的ly[][]的最小值)
4. 那么对于left[i]数组,它的左边界(j-left[i])是递增不减的,所以可以结合RMQ在O( n2 )的时间内求出来
5. 所以right[][], up[][], down[][]数组定义类似
6. 那么最终的答案为min(left[][], right[][], up[][], down[][])的和再减去包含0的部分!

感想
很遗憾本题并没有自己想出来,本来是拿它当基础练手的,洛谷的难度评定是普及+提高-,还是觉得有点难度的。这种题目如果自己可以想出来一定是个大飞跃。
另:以后写代码力争简洁,把代码写得像有水平的人写的!

代码

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<algorithm>

    using namespace std;

    #define LL long long
    #define pb push_back
    #define Set(a, v) memset(a, v, sizeof(a))
    #define For(i, a, b) for(int i = (a); i <= (int)(b); i++)
    #define Forr(i, a, b) for(int i = (a); i >= (int)(b); i--)

    #define LOG (10+5)
    #define MAXN (2000+5)

    int f[MAXN][MAXN];
    int map[MAXN][MAXN], lx[MAXN][MAXN], ly[MAXN][MAXN];

    void read(int &x){
        char ch = getchar();
        while(ch < '0' || ch > '9') ch = getchar();
        x = 0;
        while(ch >= '0' && ch <= '9'){
            x = x*10+ch-'0';
            ch = getchar();
        }
    }

    int s[MAXN], Min[MAXN][LOG], Log[MAXN];

    void Manacher(int *len, int n){
        int p0 = 0;
        For(i, 1, n){
            int pos = p0+len[p0];
            if(pos > i && len[2*p0-i]<(pos-i)) len[i] = len[2*p0-i];
            else{
                if(pos > i) len[i] = pos-i;
                while(i+len[i] < n && i-len[i] > 1 && s[i+len[i]+1]==s[i-len[i]-1]) ++len[i];
                p0 = i;
            }
        }
    }

    void makeRMQ(int x[MAXN][MAXN], int h, int n){
        Set(Min, 0x3f);
        For(i, 1, n) Min[i][0] = x[i][h];
        For(j, 1, 11) For(i, 1, n){
            if((i+(1<<j)-1) > n) break;
            Min[i][j] = min(Min[i][j-1], Min[i+(1<<(j-1))][j-1]);
        }
    }

    int query(int L, int R){
        int k = Log[R-L+1];
        return min(Min[L][k], Min[R-(1<<k)+1][k]);
    }

    int main(){
        int n, m;
        read(n); read(m);

        For(i, 2, n) Log[i] = Log[i>>1]+1;
        For(i, 1, n) For(j, 1, m) read(map[i*2-1][j*2-1]);
        n = n*2-1, m = m*2-1;

        For(i, 1, n){
            For(j, 1, m) s[j] = map[i][j];
            Manacher(lx[i], m);
        }
        For(i, 1, m){
            For(j, 1, n) s[j] = map[j][i];
            Manacher(ly[i], n);
        }

        Set(f, 0x3f);
        For(i, 1, n){
            makeRMQ(ly, i, m);
            int v = 1;
            For(j, 1, m){
                while(v<j && query(v, j) < (j-v)) v++;
                f[i][j] = min(f[i][j], j-v);
            }
            v = m;
            Forr(j, m, 1){
                while(v>j && query(j, v) < (v-j)) v--;
                f[i][j] = min(f[i][j], v-j);
            }
        }

        For(i, 1, m){
            makeRMQ(lx, i, n);
            int v = 1;
            For(j, 1, n){
                while(v<j && query(v, j) < (j-v)) v++;
                f[j][i] = min(f[j][i], j-v);
            }
            v = n;
            Forr(j, n, 1){
                while(v>j && query(j, v) < (v-j)) v--;
                f[j][i] = min(f[j][i], v-j);
            }
        }

        int ans = 0;
        For(i, 1, n) For(j, 1, m){
            if((i&1)&&(j&1)) ans += (f[i][j]>>1)+1;
            else if(!(i&1) && !(j&1)) ans += (f[i][j]+1)>>1;
        }
        printf("%d\n", ans);

        return 0;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值