【题解】 [COCI 2010/2011 #3] MONO

https://www.luogu.com.cn/problem/P6504

这道题和扫描线的关系就是鱼和自行车的关系。

二维前缀和+求多边形面积(格林公式)

该写的都在代码里了。

cpp 复制代码
// 注释 by proMatheus(hansang),有错指出 
#include<bits/stdc++.h>
using namespace std;

const int N = 510;
int R, C;
char mat[N][N];
int sum[N][N][30]; 
int V;

struct node {
	int x, y;
} pos[N];  // 多边形顶点数组 

int get_area(int r, int c, int let) { 
// 得到多边形按 (r, c) 偏移后,面积内 let 字母的个数
// 当 let = 26 时,得到的是多边形的面积 
	int res = 0;
	node pre = pos[1];
	// 按逆时针遍历所有边
	// 顺时针面积会变成负的(待会再将) 
	for (int i = V; i >= 1; i --) {
		node nxt = pos[i];
		
		if (nxt.y != pre.y) {
			// 只有竖直边才贡献
			res += sum[nxt.x + r][nxt.y + c][let]
				-  sum[pre.x + r][pre.y + c][let];
			// 核心就是遇到竖直边
			// 用后一个点到原点(左上角)矩形总值 - 前一个点到原点的矩形总值
			// 自己拿样例模拟一下就懂了 
			// 这方法好像还挺常用的,O(N) 求轴点多边形面积 
			
			// 关于为什么不能顺时针
			// 我们要保证矩形最下面的横着的边,nex 在右边点,pre 在左边点
			// 这是全局影响 res 最大的一次计算,这次加的是不是负数决定了整个 res 的正负 
			// 只有逆时针能保证以上条件 
		}
		pre = nxt;
	}
	return res;
}

int main () {
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	cin >> R >> C;
	for (int i = 0; i < R; i ++) {
		cin >> mat[i];   // 读入字母矩阵 
	}
	
	memset(sum, 0, sizeof(sum));
	for (int let = 0; let <= 26; let ++) {
		for (int i = 1; i <= R; i ++) {
			for (int j = 1; j <= C; j ++) {
				int flag = (mat[i - 1][j - 1] == 'a' + let);
				if (let == 26) {
					flag = 1;
					// let [0, 25] 对应 [a, z] 
					// 当 let = 26 时,计算的是整个矩形的面积
					// 所以和字母无关,flag 恒为 1 
				}
				
				sum[i][j][let] = flag + sum[i - 1][j][let]
					+ sum[i][j - 1][let] - sum[i - 1][j - 1][let];
				// 将 based-0 的字母矩阵转为 based-1 的二维前缀和矩阵 
			}
		}
	}
	
	int mnr = R, mxr = 0;
	int mnc = C, mxc = 0;
	
	cin >> V; 
	for (int i = 1; i <= V; i ++) {
		cin >> pos[i].y >> pos[i].x;
		// 这个阴 b读入顺序竟然是先 y 后 x
		mnr = min(mnr, pos[i].x);
		mxr = max(mxr, pos[i].x);
		mnc = min(mnc, pos[i].y);
		mxc = max(mxc, pos[i].y);
	}
	
	// 接下来将多边形平移到以 (0,0) 为原点
    // 这样多边形就变成了相对坐标,方便后续平移
    mxr -= mnr; mxc -= mnc;
    for (int i = 1; i <= V; i ++) {
    	pos[i].x -= mnr;
    	pos[i].y -= mnc;
	}
	
	node p = {mnr, C};  
    for (int i = 1; i <= V; i ++) {
        // 找到 mnr 行上最小的列坐标
        if (pos[i].x == mnr) {
            p.y = min(p.y, pos[i].y);  
        }
    }
	// 求得 p 是多边形左上角的点,这个点作为参考点
	// 平移时保证多边形内所有格子的字母,都和参考点字母一样 
    
    int ans = 0;
	int A = get_area(0, 0, 26);   // 多边形覆盖的总格子数
	
    for (int i = 0; i + mxr <= R; i ++) {
    	for (int j = 0; j + mxc <= R; j ++) {
    		// 因为 pos 里存的多边形点都以 (0,0) 为原点
			// 现在枚举 (i, j) 作为多边形平移的偏移量 
    		int let = mat[p.x + i][p.y + j] - 'a';
    		// 那么 (p.x + i, p.y + j) 就是平移后的参考点
			int res = get_area(i, j, let);
			
			ans += (res == A) ? 1 : 0;
		}
	}
	
	cout << ans << "\n";
	
	return 0;
}