题面
给定一字符图及一多边形,可任意移动多边形的位置,问有多少不同的位置使得多边形内部的字符相同。
思路
暴力方法,枚举所有可行位置\(O(n^2)\),暴力匹配\(O(n^2)\),总时间复杂度为\(O(n^4)\)
数据范围为\(500\),考虑如何将复杂度降至\(O(n^3)\)算是这个数据范围的套路
如果想对每行/列采取单项前缀和来维护一维区间内某个字符出现的数量,在匹配时枚举行/列来做到\(O(n)\)的匹配
考虑下面这个多边形格式,由于并不保证这是个凸多边形,因此还是会变成\(O(n^2)\)的匹配
于是我们考虑二维前缀和,将给定的多边形拆成多个大矩形来计算
首先便是处理出每种字母的二位前缀和,方便\(O(1)\)查询某个矩形内部的某种字母的数量
考虑下面这种情况:
由于输入数据给定的就是顺时针(上图画成了逆时针不过差不多的意思),所以就按照输入顺序处理,假设当前按字典序处理上图中的点
用\(SUM(p)\)表示点\(p\)到点\(O\)围成的矩形的某字符数量
可以发现,如果将相邻的两个点作差,可以得到一个小矩形,例如如果\(SUM(c)-SUM(b)\),将得到下图中的区域:
由于这是一个封闭的多边形,所以按顺序能够处理到\(f\rightarrow g\)的位置
如果保证相减的顺序不变,将原先的\(SUM(c)-SUM(b)\)加上\(SUM(g)-SUM(f)\)将会得到下图中的绿色区域
其中蓝色区域即为\(SUM(g)-SUM(f)\)表示的区域
由于这种减法具有正负性,蓝色区域表示的是负值,但与原先\(SUM(c)-SUM(b)\)所表示的正值相互抵消了,最后只剩下上图中的绿色区域
发现剩下的区域刚好就是需要求出的多边形内部区域
因此,对于每条边\(i\rightarrow j\)都做一次\(SUM(j)-SUM(i)\)并对结果前缀和求和,最后得出的就是多边形内部待求的值
可以根据这个方法先将多边形内部的点的数量求出,记作
其中需要限制只在\(x_i\neq x_j\)时或者\(y_i\neq y_j\)时求和,区别就在于每次做差得到的前缀和矩形主要是与\(x\)轴还是与\(y\)轴平行,二者限制其一即可,否则前后做差最后的结果将会是\(0\)
同样的,区域内有多少某种字符也是用这种方式求出
该方法匹配一次的复杂度为\(O(v)\)
一开始时找出所有给定的点中最小的\(x\)与最小的\(y\),记作\(minx,miny\),然后让所有点的\(x,y\)值减去两者,使得给定多边形外部的矩形左上角落于原点\(O\)处;然后找出所有给定的点中最大的\(x\)与最大的\(y\),记作\(maxx,maxy\),这也就是给定多边形的尺寸
其后\(O(n^2)\)枚举每个多边形左上角的可行点\((i,j)\),按上述方式进行\(O(v)\)匹配某种字符在多边形内部出现的数量,检查是否等于多边形内部点的数量即可判断是否多边形内部是否全是这种字符了
但这里不能再对于每种字符进行枚举,否则时间复杂度会变成\(O(26n^2v)\),亲测TLE
假设当前枚举的多边形左上角位置落于\((i,j)\),于是考虑随便找一个多边形内部的点,获取它的字符为何,然后判断这个字符是否占满整个多边形即可
对于如何随便获取一个多边形内部的点,在所有给定的点中找\(x=0\)时的最小\(y\)(或是\(y=0\)时的最小\(x\)),由于保证多边形每条边都与坐标轴平行,因此找到的这个点的右下网格内的字符一定是多边形内部的字符
时间复杂度为\(O(n^2v)\),注意区分本题的\(x,y\)轴的方向(别搞混)
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define perr(i,a,b) for(int i=(a);i>(b);i--)
#define all(a) (a).begin(),(a).end()
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
int n,m;
char mp[505][505];
struct point
{
int x,y;
}pr[505];
int sum[505][505][26];
void solve()
{
cin>>n>>m;
repp(i,0,n)
cin>>mp[i];
int v;
cin>>v;
int minx=1000,miny=1000;
repp(i,0,v)
{
cin>>pr[i].x>>pr[i].y;
minx=min(minx,pr[i].x);
miny=min(miny,pr[i].y);
}
repp(i,0,v) //将对应左上角变作(0,0)
{
pr[i].x-=minx;
pr[i].y-=miny;
}
int maxx=0,maxy=0;
repp(i,0,v) //获取的最大值也就是矩形的尺寸
{
maxx=max(maxx,pr[i].x);
maxy=max(maxy,pr[i].y);
}
rep(i,1,n)
rep(j,1,m)
repp(k,0,26) //处理每种字符的二维前缀和
{
sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k];
if(mp[i-1][j-1]==k+‘a‘)
sum[i][j][k]++;
}
int insidey=m-1; //获取x=0时y的最大值,便于能够获得任意一个多边形内部的点
repp(i,0,v)
if(pr[i].y==0)
insidey=min(insidey,pr[i].x);
int insidecnt=0; //内部点的数量
repp(i,0,v)
{
int j=(i+1)%v;
if(pr[i].x!=pr[j].x)
insidecnt+=(pr[j].x-pr[i].x)*pr[i].y;
}
insidecnt=abs(insidecnt);
int ans=0;
rep(i,0,n-maxy)
rep(j,0,m-maxx) //左上角可行点
{
int k=mp[i][j+insidey]-‘a‘; //内部任意字符
int cnt=0;
repp(x,0,v)
{
int y=(x+1)%v;
if(pr[x].x!=pr[y].x)
cnt+=sum[i+pr[x].y][j+pr[x].x][k]-sum[i+pr[y].y][j+pr[y].x][k];
}
if(cnt==insidecnt)
ans++;
}
cout<<ans<<‘\n‘;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}