2018.11.02校内测试liu_runda的周任飞之任(duty)

任重而道远

2018.11.02校内测试liu_runda的周任飞之任(duty)_第1张图片

2018.11.02校内测试liu_runda的周任飞之任(duty)_第2张图片

2018.11.02校内测试liu_runda的周任飞之任(duty)_第3张图片

2018.11.02校内测试liu_runda的周任飞之任(duty)_第4张图片

2018.11.02校内测试liu_runda的周任飞之任(duty)_第5张图片

Solution:

在考场上居然没想出来,大失败。。。出题人的疯狂暗示居然没有看出来。。。

树啊!!!每一个连通块都是一棵树。。对于树的基本性质你想到了什么??

边数等于点数减一,这恰好可以拿来计算连通块的数量(连通块数 = 所求子矩阵中的点数减边数)

这样只需要分别对点数和边数维护二维前缀和。

其中处理边数的时候有细节应注意:左边界那一竖列代表的还有向前一列的连边,这些边显然要减去。对于上边界也是同理。

于是我们可以对于每行和每列再维护前缀和。最后统计答案的时候容斥一波即可。

AC代码:

#include
using namespace std;

const int N = 2e3 + 5;
int G[N][N], e[N][N], p[N][N], h[N][N], s[N][N];
char ss[N];

int read () {
	int x = 0, f = 0; char c = getchar ();
	while (!isdigit (c)) f |= (c == '-'), c = getchar ();
	while (isdigit (c)) x = x * 10 + c - '0', c = getchar ();
	return f ? -x : x;
}

int main () {
	freopen ("duty.in" ,"r", stdin);
	freopen ("duty.out", "w", stdout);
	int n = read (), m = read (), Q = read ();
	for (int i = 1; i <= n; i++) {
		scanf ("%s", ss + 1);
		for (int j = 1; j <= m; j++) {
			G[i][j] = ss[j] - '0';
			if (G[i][j]) {
				p[i][j] = p[i - 1][j] + p[i][j - 1] - p[i - 1][j - 1] + 1;
				e[i][j] = e[i - 1][j] + e[i][j - 1] - e[i - 1][j - 1] + (G[i - 1][j] == 1) + (G[i][j - 1] == 1);
				h[i][j] = h[i][j - 1] + (G[i][j - 1] == 1);
				s[i][j] = s[i - 1][j] + (G[i - 1][j] == 1);
			} else {
				p[i][j] = p[i - 1][j] + p[i][j - 1] - p[i - 1][j - 1];
				e[i][j] = e[i - 1][j] + e[i][j - 1] - e[i - 1][j - 1];
				h[i][j] = h[i][j - 1], s[i][j] = s[i - 1][j];
			}
		}
	}
	
	while (Q--) {
		int a1 = read (), b1 = read (), a2 = read (), b2 = read ();
		int sww1 = p[a2][b2] + p[a1 - 1][b1 - 1] - p[a2][b1 - 1] - p[a1 - 1][b2];
		int sww2 = e[a2][b2] + e[a1][b1] - e[a2][b1] - e[a1][b2];
		int sww3 = s[a2][b1] - s[a1][b1] + h[a1][b2] - h[a1][b1];
		printf ("%d\n", sww1 - sww2 - sww3);
	}
	return 0;
}

 

你可能感兴趣的:(杂题,校内)