APIO 斑斓之地题解
APIO 斑斓之地题解
洛谷题解区域应该有我的这篇博客哦
前言:
这一道题目涉及到的算法主要是主席树,思想主要是平面图(欧拉定理)以及简单的容斥原理。如果您想要真正掌握这道题所涉及的知识点,请您先去了解这个定理以及明晰主席树的代码打法。
正文:
注明:为了表示方便,我们把蛇蛇经过的点叫做黑点吧。不经过的肯定就叫白点啦。
-
首先直接看题面,我们进行题目翻译,发现是求一个矩形内的联通快个数。当发现两个关键要素:格子图与框选矩形、查找联通块时,这道题十有八九和欧拉定理脱不了关系。(这也是非常值得积累的一种思路)。
-
接着我们就从欧拉定理的角度来思考下这道题。对于联通的图而言,始终存在着 \(V-E+F=1\) 这个公式,其中 \(V\) 是点数量 \(E\) 是边数量 \(F\) 是划分出的面的数量。这和联通块有什么关系呢?注意联通这两个字,那么对于任意一个图,我们可以看成很多个联通的图的组合,于是就有了 \(V-E+F=R\),其中 \(R\) 表示联通块个数。
-
(如果您明白怎么建立图,可以跳过这一步)有的读者可能看不懂这个图该怎么建立,而这也是很重要的一个知识点(一种思路)。因为我要求解联通块的数量,而显然的是,我们想要的是白色联通块,所以求解的点、边都应该是白色联通块内的,这个边应该是白色和白色相连接的边。
-
容斥求解:当我们明白要找一个矩形内两边都为白色点的边、白色点的数量、白色点构成的面时,我们会发现,数量有点多,暴力找的话会寄。所以我们考虑利用容斥。举个例子,我们求解白点的时候可以通过寻找黑点然后再用所有的减去白点得到答案。求解白边的时候用总共的边减去所有任意一端为黑点的边即可。求解联通块其实就是寻找四白点情况。
-
主席树解决二维数点问题:其实上面的求解就是四类二维数点问题,这里拿找边来说明。边的话,我们要分成两类(横着的和竖着的)来进行容斥,这里再拿横边举例子,为了把边看成点,我要把两个连续的点压缩成一个点,最后矩形也相应地压缩就好了。但是我们不能乱压缩,你可以想想一个十字形,都往中心去压缩肯定不行的。至于怎么用主席树解决二维数点,我专门写了个博客可以去看看思路,实在不行下面有代码。
注意事项:
本题目有多点注意事项:
- 数组大小要开够。
- 联通块求解时要分析清楚最后是否要多一(完全将蛇蛇覆盖但是有空隙)。
- 有可能存在字符串为空的情况,这时候注意输入可能会导致错位。
代码实现:
本人调了四个小时。
不知道为什么注释变成乱码了……(但是懒得改了,思路很清晰了)
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct node{
int val;
int ls;
int rs;
}tree[5][8000006];
int cnt[5];
int roots[5][8000006];
void pushup(int id,int rt){
tree[id][rt].val=tree[id][tree[id][rt].ls].val+tree[id][tree[id][rt].rs].val;
}
void build(int id,int rt,int L,int R){
int mid=(L+R)>>1;
if(L==R){
tree[id][rt].val=0;
return ;
}
tree[id][rt].ls=++cnt[id];
tree[id][rt].rs=++cnt[id];
build(id,tree[id][rt].ls,L,mid);
build(id,tree[id][rt].rs,mid+1,R);
}
int newnode(int id,int root){
int u=++cnt[id];
tree[id][u]=tree[id][root];
return u;
}
int add(int id,int root,int newrt,int pos,int v,int le,int ri){
if(newrt==root){
newrt=newnode(id,root);
}
int mid=(le+ri)>>1;
if(le==ri){
tree[id][newrt].val+=v;
return newrt;
}
if(pos<=mid){
tree[id][newrt].ls=add(id,tree[id][root].ls,tree[id][newrt].ls,pos,v,le,mid);
}
else{
tree[id][newrt].rs=add(id,tree[id][root].rs,tree[id][newrt].rs,pos,v,mid+1,ri);
}
pushup(id,newrt);
return newrt;
}
int query(int id,int rt,int L,int R,int le,int ri){
int mid=(le+ri)>>1;
if(le>=L&&ri<=R){
return tree[id][rt].val;
}
if(le>R||ri<L){
return 0;
}
int ret=0;
ret+=query(id,tree[id][rt].ls,L,R,le,mid);
ret+=query(id,tree[id][rt].rs,L,R,mid+1,ri);
return ret;
}
struct nod{
int x;
int y;
bool operator ==(const nod a)const{
return a.x==x&&a.y==y;
}
}dot[5][8000006];
bool cmp(nod x,nod y){
if(x.x!=y.x)return x.x<y.x;
else return x.y<y.y;
}
int sum[5];
int n,m,len,qq;
void adddot(int x,int y){
for(int i=1;i<=4;i++){
sum[i]++;
dot[i][sum[i]].x=x;
dot[i][sum[i]].y=y;
}
if(x<n){
sum[2]++;
dot[2][sum[2]].x=x+1;
dot[2][sum[2]].y=y;
sum[4]++;
dot[4][sum[4]].x=x+1;
dot[4][sum[4]].y=y;
}
if(y<m){
sum[3]++;
dot[3][sum[3]].x=x;
dot[3][sum[3]].y=y+1;
sum[4]++;
dot[4][sum[4]].x=x;
dot[4][sum[4]].y=y+1;
}
if(x<n&&y<m){
sum[4]++;
dot[4][sum[4]].x=x+1;
dot[4][sum[4]].y=y+1;
}
}
int getsum(int id,int n1,int m1,int n2,int m2){
if(n1>n2||m1>m2){
return 0;
}
int x1=query(id,roots[id][n2],m1,m2,1,m);
int x2=query(id,roots[id][n1-1],m1,m2,1,m);
// cout<<id<<" "<<x1<<" "<<x2<<endl;
return x1-x2;
}
signed main(){
ios::sync_with_stdio(false);
roots[1][0]=1;
roots[2][0]=1;
roots[3][0]=1;
roots[4][0]=1;//ËĸöÖ÷ϯÊ÷
cnt[1]=cnt[2]=cnt[3]=cnt[4]=1;
cin >>n >>m >>len >>qq;
build(1,roots[1][0],1,m);
build(2,roots[2][0],1,m);
build(3,roots[3][0],1,m);
build(4,roots[4][0],1,m);
int sn,sm;
cin >> sn>>sm;
adddot(sn,sm);
string s;
if(len)
cin >> s;
s=" "+s;
int maxn=sn;
int minn=sn;
int maxm=sm;
int minm=sm;
for(int i=1;i<=len;i++){
if(s[i]=='N'){
sn--;
}
if(s[i]=='S'){
sn++;
}
if(s[i]=='E'){//->
sm++;
}
if(s[i]=='W'){//<-
sm--;
}
adddot(sn,sm);
maxn=max(maxn,sn);
minn=min(minn,sn);
maxm=max(maxm,sm);
minm=min(minm,sm);
}
// cout<<"TT"<<sum[2]<<endl;
//
for(int k=1;k<=4;k++){
sort(dot[k]+1,dot[k]+1+sum[k],cmp);
sum[k]=unique(dot[k]+1,dot[k]+1+sum[k])-dot[k]-1;
// for(int i=1;i<=sum[2];i++){
// cout<<"TEST2: "<<dot[2][i].x<<" "<<dot[2][i].y<<endl;
// }
// cout<<endl;
int nowh=0;
for(int i=1;i<=sum[k];i++){
int t=dot[k][i].y;
if(!roots[k][dot[k][i].x]){
for(int j=nowh+1;j<=dot[k][i].x-1;j++){//ÖмäûÓõĵĸ´ÖƹýÀ´
roots[k][j]=roots[k][j-1];
}
nowh=dot[k][i].x;
roots[k][dot[k][i].x]=add(k,roots[k][dot[k][i].x-1],roots[k][dot[k][i].x-1],dot[k][i].y,1,1,m);
}
else{
add(k,roots[k][dot[k][i].x-1],roots[k][dot[k][i].x],dot[k][i].y,1,1,m);
}
}
for(int j=nowh+1;j<=n;j++){//ÖмäûÓõĵĸ´ÖƹýÀ´
roots[k][j]=roots[k][j-1];
}
}
// cout<<qq<<endl;
// for(int i=1;i<=4;i++){
// for(int j=1;j<=sum[i];j++){
// if(dot[i][j].x==dot[i][j-1].x){
// continue;
// }
// cout<<roots[i][dot[i][j].x]<<" ";
// }
// cout<<endl;
// }
while(qq--){
int n1,m1,n2,m2;
cin >>n1>>m1>>n2>>m2;
int N=(n2-n1+1);
int M=(m2-m1+1);
// cout<<fir<<endl;
int ans1=N*M-getsum(1,n1,m1,n2,m2);//ÕâÀï¼ÆËãµÄÊÇ¿òÑ¡ÄÚÈÝÖÐÍÁµØµãµÄ¸öÊý
int ans2=(N-1)*M-getsum(2,n1+1,m1,n2,m2);
int ans3=N*(M-1)-getsum(3,n1,m1+1,n2,m2);
int ans4=(N-1)*(M-1)- getsum(4,n1+1,m1+1,n2,m2);
if(n1<minn&&n2>maxn&&m1<minm&&m2>maxm)ans4++;
// cout<<"TEST"<<ans1<<" "<<ans2<<" "<<ans3<<" "<<ans4<<endl;
cout<<ans1-ans2-ans3+ans4<<endl;
}
}