原题链接
AtCoder题目以思维难度高而著名,据说大多数难题都没用到什么高级算法。代码量不大,难点在分析性质上,有的题想一天都想不到怎么做。问题描述言简意赅,有些题目的解法创意令人赞叹,能够锻炼思维,学到不少东西。可以尝试一些该网站上的进阶题目。
题目大意 给定$N$个内角要么90度要么270度的简单多边形(任意两条不相邻的边不能相交),多边形之间可以重叠
第$i$个多边形描述为按边连接的某个方向(顺逆时针)顺序给出$M_{i}$个顶点坐标(保证在第一象限和$x$/$y$正半轴),第一个点与最后一个点相连
每一个多边形的点符合以下要求
随后有$Q$次询问,每次给出一个坐标$(x, y)$,求出点$(x+0.5,y+0.5)$上有多少个多边形
题解 当时想了几种方法都行不通,凭感觉认为应该把区间修改查询(树状数组或者线段树)作为核心,但此题情景不知道该怎么用,因此也不确定是不是正解。
此题先给出所有信息再做出询问,中途不会对信息做修改,因而可以离线处理
关键点:由于题目中多边形较为特殊,每一条边都会区分多边形内外,而边只可能平行于$x$轴或$y$轴,可以很方便的只按一个方向考虑多边形覆盖情况 ,这里以一条与$y$轴平行的直线为例,该直线的上下一对顶点可以作为 “之后就是这个多边形的外界” 或是 “之后就是这个多边形的内部” 的标志。
接下来考虑如何确定直线的标志类别
只考虑与$y$轴平行直线的情况下,不难发现外界和内部标志是交替进行 的,而多边形最靠左的边必然是内部开始标志
结合前面的离线处理可以想到将点按横坐标$x$离散存储 ,再按$x$轴正方向扫描式更新平行于$y$轴的直线上的多边形覆盖区间情况,同时处理这条直线上询问过的点,将答案存于数组最后统一输出。
这里选用树状数组作为区间修改查询工具,区间修改的操作也要按横坐标$x$离散存储以便扫描时有序执行。巧合的是,按照点的顺序,树状数组区间修改对应的单点修改值的正负也是交替的 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <iostream> #include <algorithm> #include <cstring> #include <vector> using namespace std;#define fastIO ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define fi first #define se second typedef pair<int ,int > PII;const int N=1e6 +10 ;int n,m,k;int bit[N],ans[N];PII a[N]; vector<PII> c[N],d[N]; void update (int x,int k) { for (int i=x;i<=1e5 +10 ;i+=i&-i) bit[i]+=k; } int query (int x) { int sum=0 ; for (int i=x;i;i-=i&-i) sum+=bit[i]; return sum; } int main () { fastIO cin>>n; for (int i=1 ;i<=n;i++){ cin>>k; int p=0 ; int x,y; for (int j=0 ;j<k;j++){ cin>>x>>y; x++,y++; a[j]={x,y}; if (a[j]<a[p]) p=j; } int f=1 ; for (int j=0 ;j<k;j++){ c[a[(p+j)%k].fi].push_back ({a[(p+j)%k].se,f}); f*=-1 ; } } cin>>m; for (int i=1 ;i<=m;i++){ int x,y; cin>>x>>y; x++,y++; d[x].push_back ({y,i}); } for (int i=1 ;i<=1e5 +10 ;i++){ for (auto t:c[i]){ int l=t.fi,x=t.se; update (l,x); } for (auto t:d[i]){ int y=t.fi,x=t.se; ans[x]=query (y); } } for (int i=1 ;i<=m;i++) cout<<ans[i]<<endl; return 0 ; }