【题解】打地鼠 SDOI2011 模拟 行列无关

Prelude

为什么洛谷上的题解都是剪枝做的啊!就没有人写复杂度靠谱的算法吗!
传送到洛谷:( ̄、 ̄)
传送到BZOJ:( ´・・)ノ(._.`)
本篇博客地址:o(><;)oo


Solution

首先\(O(n^6)\),或者是\(O(n^4 \log^2 n)\)的模拟非常好想,枚举锤子的长宽,然后从左上角开始挨个砸就可以了。
枚举的复杂度是\(O(n^2)\)的,模拟一次的复杂度是\(O(n^4)\)的,也可以用BIT做到一次模拟\(O(n^2 \log^2 n)\)
仔细想了想发现似乎没法合理枚举,那就只能发掘性质了。
直觉告诉我似乎是行列无关的。
具体来说,我们首先固定锤子的长为1,然后枚举锤子的宽,求出当长为1的时候最大可行的宽,叫做\(c\)
然后再固定锤子的宽为1,枚举锤子的长,求出当宽为1的时候最大可行的长,叫做\(r\)
上面两步可以用\(O(n^4)\)的暴力模拟来做,或者是用BIT做到\(O(n^3 \log n)\)
那么这个\(r\)\(c\)就是最终答案。
试着证明了一下,确实是这样的,具体证明我没有仔细想,大概感觉是从“每个格子被敲打的次数是行列无关的”这条入手?
然后就A掉了。
因为我比较懒,所以写的是\(O(n^4)\)的方法,毕竟这个模拟常数小嘛,\(O(n^4)\)过100肯定没问题啦。


Code

#include <cstring>
#include <algorithm>
#include <cstdio>

using namespace std;
const int MAXN = 110;
int _w;

int n, m, a[MAXN][MAXN], tot;
int r, c, b[MAXN][MAXN];
int t[MAXN][MAXN];

bool check( int x ) {
    for( int i = 1; i <= r; ++i )
        for( int j = 1; j <= c; ++j )
            t[i][j] = b[i][j];
    for( int i = 1; i <= r; ++i )
        for( int j = 1; j <= c-x+1; ++j )
            for( int k = j+x-1; k >= j; --k )
                t[i][k] -= t[i][j];
    for( int i = 1; i <= r; ++i )
        for( int j = 1; j <= c; ++j )
            if( t[i][j] ) return false;
    return true;
}

void solve() {
    r = n, c = m;
    for( int i = 1; i <= r; ++i )
        for( int j = 1; j <= c; ++j )
            b[i][j] = a[i][j];
    for( int x = c; x >= 1; --x )
        if( check(x) ) {
            tot /= x;
            break;
        }
    r = m, c = n;
    for( int i = 1; i <= r; ++i )
        for( int j = 1; j <= c; ++j )
            b[i][j] = a[j][i];
    for( int y = c; y >= 1; --y )
        if( check(y) ) {
            tot /= y;
            break;
        }
    printf( "%d\n", tot );
}

int main() {
    _w = scanf( "%d%d", &n, &m );
    tot = 0;
    for( int i = 1; i <= n; ++i )
        for( int j = 1; j <= m; ++j ) {
            _w = scanf( "%d", &a[i][j] );
            tot += a[i][j];
        }
    solve();
    return 0;
}
posted @ 2017-12-20 20:14 mlystdcall 阅读(...) 评论(...) 编辑 收藏