noip模拟赛 入阵曲

分析:其实很容易想到O(n^3m^3)的算法,枚举x1,x2,y1,y2,再统计一下和.求和可以用前缀和,能优化到O(n^2m^2),能得到60分.对于特殊性质的点,求一下a[i][j]与k的最小公倍数lcm,就可以推出来要选多少个点,乘法原理推一下就能解决了.

      满分做法的思想是降维,先分析一下一维怎么做.问题要求满足(a[l] + a[l + 1] + ...... + a[r]) % k = 0的区间[l,r]有多少个.利用前缀和优化就是(sum[r] - sum[l - 1]) % k = 0.对约束进行变形:sum[r] % k = sum[l - 1] % k. O(n)的扫一遍,记录当前的sum[i] % k,看前面有多少个和它相同的就可以了.

      转化到二维上,为了用上一维的做法,固定矩形的上边界和下边界,把每一列看做是一个元素a[i],就可以用上一维的做法了,是一个非常常见的变形.

      求子矩阵问题的常用思路是先转化到1维上进行处理,再把行或列压一下,就能把2维放到1维上处理了,数学式子一定要会变形!

75分暴力:

 

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

using namespace std;

typedef long long ll;

ll n, m, k, a[410][410], sum[410][410], t, ans;
bool flag = true;

void solve2()
{
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int p = i; p <= n; p++)
                for (int q = j; q <= m; q++)
                {
        ll temp = sum[p][q] - sum[i - 1][q] - sum[p][j - 1] + sum[i - 1][j - 1];
        if (temp % k == 0)
            ans++;
                }
    printf("%lld\n", ans);
}

ll gcd(ll a, ll b)
{
    if (!b)
        return a;
    return gcd(b, a % b);
}

void solve1()
{
    ll lcm = a[1][1] / gcd(a[1][1], k) * k;
    ll res = lcm / a[1][1];
    ll temp = 0;
    while (res <= n * m)
    {
        temp = 0;
        for (ll i = 1; i <= n; i++)
            if (res % i == 0 && (res / i) <= m)
                temp += (n - i + 1) * (m - res / i + 1);
        //printf("%lld %lld\n", res, temp);
        ans += temp;
        res += lcm / a[1][1];
    }
    printf("%lld\n", ans);
}

int main()
{
    scanf("%lld%lld%lld", &n, &m, &k);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
        scanf("%lld", &a[i][j]);
        if (!(i == 1 && j == 1) && a[i][j] != t)
            flag = false;
        t = a[i][j];
        sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
        }
    if (flag)
        solve1();
    else
        solve2();

    return 0;
}

 

正解:

 

 

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

using namespace std;

typedef long long ll;

ll n, m, k, a[410][410], sum[410][410], ans, cnt[1000010];

int main()
{
    scanf("%lld%lld%lld", &n, &m, &k);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
        {
            scanf("%lld", &a[i][j]);
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
        }
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j <= n; j++)
        {
            cnt[0] = 1;
            for (int kk = 1; kk <= m; kk++)
            {
                ll p = (sum[j][kk] - sum[i][kk] + k) % k;
                ans += cnt[p];
                cnt[p]++;
            }
            for (int kk = 1; kk <= m; kk++)
                cnt[(sum[j][kk] - sum[i][kk] + k) % k] = 0;
        }
    printf("%lld\n", ans);

    return 0;
}

 

posted @ 2017-11-06 22:08  zbtrs  阅读(227)  评论(0编辑  收藏  举报