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; }