P3683 题解
前言
NOIP 考前写点题解攒 RP
题解
注意事项:一个操作里面包含两个步骤。
首先观察操作的性质:
-
整个地图的所有单元的编码集合恰好与
0 到2^{2n}-1 里的每一个数一一对应。 -
当进行了前
i 次操作时,已经确定了二进制的前2i 位,此时满足存在一个极大矩形,满足只有这里面的单元的编码的前2i 位与前i 次操作形成的2i 位编码相同。即在这个矩形内的单元的编码排序后连续。另外,随着模拟操作可以找到这个矩形的所在位置。
(不会的话康康样例
由于题目询问的“
看起来还是没有头绪,注意到区间在编码上连续,而多边形在地图上连续,我们不妨把二维抽象成一维(降维打击.jpg)来考虑问题,其中一维中的第
那么如果把多边形覆盖到的单元在一维上染色,那么我们就得到了这样一张图:
(这是样例
那么注意到“只用一个区间恰好包含多边形的最小长度”的区间的左右端点恰好是多边形内编号最小和最大的单元。 这个易于求解,先按下不表。
我们再考虑另一个问题:“在这个区间内前
阿巴阿巴。。。还是不大会,我们考虑一下二维地图和一维序列之间的关系。
为了简便操作,我们希望我们考虑的间隔区间在二维和一维上都连续——我们发现按照题目操作所分割出来的矩形正好满足我们所需要的条件。但是间隔区间可能不完全等于题目操作分割出来的矩形所形成的区间,一个比较 fake 的想法就是分治。
我们假设已经进行了前
-
若这个矩形内的多边形不存在顶点,即在矩形内部的多边形的边一定都是横边或者竖边。为了方便说明,假设是横边。那么在进行步骤一的时候把矩形分成了左右两部分,这两部分相似。因为右边的矩形的单元格编号减去
2^{i+1} 时与左边的矩形相应位置的单元格编号一样。并且经过左右矩形的横线的位置一样,且都是横跨整个矩形。 -
那么我们只需要计算一边的贡献。不过怎么合并?注意到唯一有影响的区间就是横跨左边和右边矩形的间隔。如果把这个矩形表示成一个区间,那么就相当于左矩阵区间的右半部分和右矩形区间的左半部分合并。因此我们只需要维护在区间端点的间隔即可。
-
当这个矩形内的多边形存在顶点时或者切的方向与当前矩形内的边平行时,,我们无法快速计算,就暴力递归下去。
看起来很暴力,不过时间复杂度是对的。
首先注意我们的分治最多会递归
对单个矩形分别考虑。最多拥有
这样的矩形共存在
代码实现
-
递归边界:里面没有边或者边长为
1 。由于里面的点的所属(在矩形内部/外部)相同,可以对一个点用射线法:任取一个方向,若经过的边数是奇数,则在矩形内部,否则在外部。我只有每次在处理分割线竖直的时候才会使用上述方法,否则可以通过分割的边(预处理出来每一条边的左边/右边属于多边形内部)推断出来。 -
我们维护当前矩形的任意一个靠近边界的点的属于多边形的情况,记为
in,当分割的时候(假设竖着切),那么左矩形的一个点的情况可以由当前矩形的in和右矩形最靠左的横边推过来。若这个矩形的in没有初值,那么他一定在多边形外侧,那么分割的另外一个矩形一定有横边。所以正确性可以保证。特判长度为1 的情况即可。 -
维护间隔区间:我们可以用
pair<int,int>来记录这个矩形的左端点到first-1的间隔和second+1到右端点的间隔。特别的,任意一个值为-1 表示整个区间都是间隔。在合并间隔时计算贡献即可。(因为这个区间不会对其他区间产生贡献)
代码:
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
using namespace std;
const int N=1e6+1e3;
struct Seg{int dir,p,l,r;Seg(int dir=-1,int p=-1,int l=-1,int r=-1):dir(dir),p(p),l(l),r(r){}};
const bool operator<(const Seg&x,const Seg&y){return x.p<y.p;}
/*
Seg 表示是边
假设是横边,p 表示这条边的 y 左边,l,r 表示这条边的横坐标
dir=0 表示若这条边是竖边,那么在这条边左边(在这条边左边的边的右边,下类似)的点在矩形内部
表示若这条边是横边,那么在这条边下面的点在矩形内部
dir=1 表示若这条边是竖边,那么在这条边右边的点在矩形内部
表示若这条边是横边,那么在这条边上面的点在矩形内部
*/
map<int,int,greater<int> >mp;
int check(int l,int r,vector<Seg>&b){for(auto y:b)if(y.l>l||y.r<r)return 0;return 1;}
//判断是否有顶点,因为有可能顶点在矩形边上,另一条边被吞了。
void write(vector<Seg>&x){for(auto y:x)printf("%d %d %d %d\n",y.dir,y.p,y.l,y.r);puts("");}
pair<int,int>solve(int l,int r,int &sx,int &ex,int &sy,int &ey,int in,int f,vector<Seg>&a,vector<Seg>&b)
/*
l,r:这个矩形对应的区间编号
sx,ex,sy,ey:假定矩形的一维为 sx,ex,另一维为 sy,ey,
并且满足分割的横线的按 x 这一维分成两半
in:若这个矩形内部没有边,那么这个矩形是在多边形内部还是外部
f:系数,表示求出的间隔数变成 f 倍
a:维护的边集合,满足与当前分割的横线平行
b:维护的边集合,满足于当前分割的横线相交
pair<int,int>solve() 表示当前的矩形的区间的左端点到 solve().fi-1 是间隔
表示当前的矩形的区间 solve().se+1 到左断点是间隔
-1 表示这个区间都是间隔。
记得每次转坐标,另外,这个不是 90 度的转,是反复转。
*/
{
if(a.empty()&&b.empty())
{if(in==-1)exit(1);return in?mk(l,r):mk(-1ll,-1ll);}
if(l==r)
if(a.empty())return solve(l,r,sy,ey,sx,ex,in,f,b,a);
else return (a[0].dir==(a[0].p==sx))?mk(l,r):mk(-1ll,-1ll);
int mid=l+r>>1,m=sx+ex>>1;
if(a.empty()&&check(sx,ex,b))//边与分割线相交
{
pii t=solve(l,mid,sy,ey,sx,m,in,2*f,b,a);
if(t.fi==-1)return t;
mp[t.fi-l+mid-t.se]+=f;return mk(t.fi,t.se-l+mid+1);
}
else //有顶点或者边与分割线平行
{
vector<Seg>al,ar,bl,br;
for(auto y:a)
{
if(y.p<=m)al.pb(y);
if(y.p>=m)ar.pb(y);
}
for(auto y:b)
{
if(y.l<m)bl.pb(y);
if(y.r>m)br.pb(y);
}
pii tl=solve(l,mid,sy,ey,sx,m,(!ar.empty()?ar[0].dir==0:in),f,bl,al);
pii tr=solve(mid+1,r,sy,ey,m,ex,(!al.empty()?al.back().dir==1:in),f,br,ar);
if(tl.fi==-1)return tr;
if(tr.fi==-1)return tl;
mp[tr.fi-tl.se-1]+=f;return mk(tl.fi,tr.se);
}
}
int n,m;
int val[N],s1[N],s2[N];
vector<Seg>A,B;
int get(int x,int y,int tot=0)
/*
get()=1 :在矩形内部
get()=0 :在矩形外部
*/
{
for(auto i:A)if(i.p<=x&&i.l<=y&&i.r>y)tot++;
return tot&1;
}
signed main()
{
// freopen("geohash.in","r",stdin);
// freopen("geohash.out","w",stdout);
scanf("%lld%lld",&n,&m);
int px,py,x,y,sx,sy;
scanf("%lld%lld",&sx,&sy);px=sx,py=sy;
//A:维护横边,B:维护竖边
for(int i=2;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
if(x==px)A.pb(Seg(0,x,min(py,y),max(py,y)));
else B.pb(Seg(0,y,min(px,x),max(px,x)));
px=x,py=y;
}
if(x==sx)A.pb(Seg(0,x,min(sy,y),max(sy,y)));
else B.pb(Seg(0,y,min(sx,x),max(sx,x)));
sort(A.begin(),A.end());sort(B.begin(),B.end());
for(int i=0;i<A.size();i++)A[i].dir=get(A[i].p,A[i].l);
for(int i=0;i<B.size();i++)B[i].dir=get(B[i].l,B[i].p);
sx=0,sy=0;int ex=1<<n,ey=1<<n;
pii t=solve(0,(1ll<<n*2)-1,sx,ex,sy,ey,-1,1,A,B);
//维护出来的 t.fi 和 t.se 就是用一个区间包含多边形的最小长度
int res=t.se-t.fi+1,cnt=0;
for(auto y:mp)
{
val[++cnt]=y.fi;
s1[cnt]+=s1[cnt-1]+y.se;
s2[cnt]+=s2[cnt-1]+y.fi*y.se;
}
int _;scanf("%lld",&_);
while(_--)
{
int k;scanf("%lld",&k);k--;
int it=lower_bound(s1+1,s1+cnt+1,k)-s1;
printf("%lld\n",res-(s2[it-1]+(k-s1[it-1])*val[it]));
}
return 0;
}
后记
难点主要在于模拟。