@codeforces - 1186E@ Vus the Cossack and a Field


@description@

给定一个 n*m 的 01 矩阵,通过这个矩阵生成一个无穷矩阵,具体操作如下:

(1)将这个矩阵写在左上角。
(2)将这个矩阵每位取反写在右上角。
(3)将这个矩阵每位取反写在左下角。
(4)将这个矩阵写在右下角。
(5)将得到的矩阵再作为初始矩阵,重复这些操作。

比如对于初始矩阵:

\[\begin{matrix} 1 & 0 & \\ 1 & 1 & \\ \end{matrix} \]

它操作一次变为:

\[\begin{matrix} 1 & 0 & 0 & 1 \\ 1 & 1 & 0 & 0 \\ 0 & 1 & 1 & 0 \\ 0 & 0 & 1 & 1 \\ \end{matrix} \]

再操作一次变为:

\[\begin{matrix} 1 & 0 & 0 & 1 & 0 & 1 & 1 & 0 \\ 1 & 1 & 0 & 0 & 0 & 0 & 1 & 1 \\ 0 & 1 & 1 & 0 & 1 & 0 & 0 & 1 \\ 0 & 0 & 1 & 1 & 1 & 1 & 0 & 0 \\ 0 & 1 & 1 & 0 & 1 & 0 & 0 & 1 \\ 0 & 0 & 1 & 1 & 1 & 1 & 0 & 0 \\ 1 & 0 & 0 & 1 & 0 & 1 & 1 & 0 \\ 1 & 1 & 0& 0 & 0 & 0 & 1 & 1 \\ \end{matrix} \]

然后继续操作……

我们将左上角记作 (1, 1)。
现在进行 q 次询问,每次询问以 (x1, y1) 为左上角,(x2, y2) 为右下角的矩阵内所有元素的和。

input
第一行包含三个整数 n, m, q (1 <= n, m <= 1000, 1 <= q <= 10^5)。
接下来 n 行每行包含 m 个字符 cij,描述初始矩阵。
接下来 q 行每行包含四个整数 x1, y1, x2, y2,描述一个询问。

output
对于每个询问,输出答案。

sample input 1
2 2 5
10
11
1 1 8 8
2 4 5 6
1 2 7 8
3 3 6 8
5 6 7 8
sample output 1
32
5
25
14
4

sample input 2
2 3 7
100
101
4 12 5 17
5 4 9 4
1 4 13 18
12 1 14 9
3 10 7 18
3 15 12 17
8 6 8 12
sample output 2
6
3
98
13
22
15
3

@solution@

要用递归打败递归!

考虑将一个询问拆成前缀和作差的形式,则询问相当于转换为 (1, 1) 到 (x, y) 的前缀和。
如果直接递归分治求解显然就直接炸了,所以我们要发现一些性质。

我们不妨记初始矩阵为 M0,操作一次后的矩阵为 M1,操作两次后的矩阵为 M2,……,以此类推。
观察题目中给出的样例 M1, M2,不难发现每一行 1 的个数都相等,且都等于这一行元素总数的一半;列同理。
这样我们可以快速求解出 Mi 所有元素的和,并求解出 Mi 前若干行/列所有元素的和(这里 i = 0 要特殊处理,因为没有这个性质)

设 Mi 包含 (x, y),考虑 (x, y) 位置:
(1)在 Mi 左上部分,直接递归到 M(i-1)。
(2)在 Mi 右上部分,此时询问包含两部分:在左上部分有一些完整的行,在右上部分是一个规模较小的子问题。左上直接用公式算,右上递归。
(3)在 Mi 左下部分,大致同(2)的处理。
(4)在 Mi 右下部分,一样考虑左上、左下、右上、右下分别是什么形状,发现只有右下是跟原题类似的,规模较小的子问题。然后大概同(2)的处理。
一样当 i = 0 要特殊处理。

发现每一次都只会向下递归一次,递归一次规模至少减半,所以一次询问是 O(log) 的时间复杂度。

具体可能代码更具说服力。

@accepted code@

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1000;
ll s[MAXN + 5][MAXN + 5];
int n[MAXN + 5], m[MAXN + 5];
char c[MAXN + 5];
ll fun1(int dep, int x, int y) {
	if( dep == -1 ) return s[x][y];
	if( x <= n[dep] && y <= m[dep] )
		return fun1(dep - 1, x, y);
	if( x <= n[dep] && y > m[dep] ) {
		if( dep ) return (1LL*x*(y-m[dep]) - fun1(dep - 1, x, y-m[dep])) + 1LL*x*m[dep-1];
		else return (1LL*x*(y-m[dep]) - fun1(dep - 1, x, y-m[dep]) + fun1(dep - 1, x, m[dep]));
	}
	if( x > n[dep] && y <= m[dep] ) {
		if( dep ) return (1LL*(x-n[dep])*y - fun1(dep - 1, x-n[dep], y)) + 1LL*n[dep-1]*y;
		else return (1LL*(x-n[dep])*y - fun1(dep - 1, x-n[dep], y) + fun1(dep - 1, n[dep], y));
	}
	if( x > n[dep] && y > m[dep] ) {
		//puts("?");
		if( dep ) {
			//printf("? %lld %lld %lld\n", fun2(dep - 1, x-n[dep]), fun3(dep - 1, y-m[dep]), 1LL*n[dep]*m[dep]/2);
			return fun1(dep - 1, x-n[dep], y-m[dep]) + 1LL*(x-n[dep])*m[dep-1] + 1LL*n[dep-1]*(y-m[dep]) + 1LL*n[dep]*m[dep]/2;
		}
		else {
			//printf("| %lld %lld %lld %lld\n", 1LL*x*y, (1LL*(x-n[dep])*(y-m[dep]) - fun1(dep - 1, x-n[dep], y-m[dep])), fun1(dep - 1, x-n[dep], m[dep]) + fun1(dep - 1, n[dep], y-m[dep]), s[n[0]][m[0]]);
			return 1LL*(x-n[dep])*m[dep] + 1LL*n[dep]*(y-m[dep]) + fun1(dep - 1, x-n[dep], y-m[dep]) + s[n[0]][m[0]] - fun1(dep - 1, x-n[dep], m[dep]) - fun1(dep - 1, n[dep], y-m[dep]);
		}
	}
}
ll sum(int x, int y) {
	if( x == 0 || y == 0 ) return 0;
	int dep; for(dep = 0; x - n[dep] > n[dep] || y - m[dep] > m[dep]; dep++);
	return fun1(dep, x, y);
}
int main() {
	int q;
	scanf("%d%d%d", &n[0], &m[0], &q);
	for(int i=1;i<=n[0];i++) {
		scanf("%s", c + 1);
		for(int j=1;j<=m[0];j++)
			s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + (c[j] - '0');
	}
	for(int i=1;i<=MAXN;i++)
		n[i] = 2*n[i-1], m[i] = 2*m[i-1];
	for(int i=1;i<=q;i++) {
		int x1, y1, x2, y2;
		scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
		printf("%lld\n", sum(x2, y2) - sum(x1-1, y2) - sum(x2, y1-1) + sum(x1-1, y1-1));
	}
}

@details@

好久没打 CF 了……活动活动手脚。
不过被 B 题坑了。。。一个假题做了我将近 1 个小时。
大概想着我可能要掉成灰了,这时 CF 突然发表通知,说 B 题标算有误,然后整道题就凭空消失了。还好最后 unrated 了,不然亏死 www。

这道题不难,但非常有 CF 的风格。

posted @ 2019-06-28 23:58  Tiw_Air_OAO  阅读(332)  评论(0编辑  收藏  举报