【题目】
原题地址
题目大意:给出 n n 个点 (x,y) ( x , y ) ,有 q q 个操作 (a,b) ( a , b ) ,先求出有多少个不被栅栏挡住的点满足 x<=a x <= a 并且 y<=b y <= b ,然后以 (a,b) ( a , b ) 这个点作为右上角形成一个7字型的栅栏,遇到之前的栅栏或者坐标轴就结束。
【题目分析】
一开始看这道题,感觉是线段树在 x x 轴和 y y 轴维护交点之类的,然后容斥计算贡献。但是想着想着发现这个交点实在是维护不了,如果用二维线段树倒是可以,不过MLE得飞起。
接着想用splay维护一下一个楼梯形轮廓,不过还是没有好的思路。
于是弃疗看题解,发现居然就只是一个 splay s p l a y +扫描线,太厉害了!
【解题思路】
如果我们能处理出每个围栏区间内有多少个贡献点,那么我们只需要倒序移走围栏,用DSU或者dp维护贡献就可以得到最后答案。
我们考虑将所有节点按 y y 从大到小排序,那么如果我们遇到一个围栏节点,等价于在 x x 这个位置插入一个节点,
而遇到一个贡献点,等价于在 x x 这个位置后面找一个最近的围栏节点。
因此这个东西可以用 splay s p l a y 来维护。
接下来如果我们只考虑两个点 (x,y,t1)(a,b,t2) ( x , y , t 1 ) ( a , b , t 2 ) ,其中 x<a,y>b x < a , y > b ,那么会有两种围栏的情况。
若 t1<t2 t 1 < t 2 ,则我们遇到 (a,b,t2) ( a , b , t 2 ) 时仍然会在splay保留 (x,y,t1) ( x , y , t 1 ) ;
若 t2>t1 t 2 > t 1 ,则接下来,我们遇到的所有贡献点不会对 (x,y,t1) ( x , y , t 1 ) 产生贡献,因此可以删去这个点。
这是遇到围栏点的情况,遇到贡献点的话我们直接在 splay s p l a y 中找到 x x 比它大的第一个围栏点即可。
最后的DSU和dp维护要注意合并的顺序,是大的t往小的t进行合并。
【参考代码】
#include
using namespace std;
const int N=3e5+10;
const int M=N*4;
int n,m,tot;
int las[N],cnt[N],f[N],ans[N];
struct Tdata
{
int x,y,id;
Tdata(){}
Tdata(int x,int y,int id):x(x),y(y),id(id){}
}a[M];
bool cmp(Tdata A,Tdata B)
{
if(A.y^B.y)
return A.y>B.y;
return A.xstruct Tele
{
int x,t;
Tele(){}
Tele(int x,int t):x(x),t(t){}
bool operator <(const Tele &A)const{
if(x^A.x)
return xreturn tstruct Splay
{
int root,len;
int ch[M][2],fa[M];
Tele val[M];
void rotate(int x)
{
int y=fa[x],z=fa[y],kind=ch[fa[x]][1]==x,t=ch[x][!kind];
ch[y][kind]=t;ch[x][!kind]=y;
if(t)
fa[t]=y;
fa[x]=z;
if(z)
ch[z][ch[fa[y]][1]==y]=x;
fa[y]=x;
}
void splay(int x)
{
while(fa[x])
{
int y=fa[x];
if(fa[y])
{
if(ch[fa[x]][1]==x^ch[fa[y]][1]==y)
rotate(x);
else
rotate(y);
}
rotate(x);
}
root=x;
}
void insert(int x,int t)
{
val[++len]=Tele(x,t);
if(!root)
{
root=len;
return;
}
int now=root;
while(true)
{
if(val[len]if(!ch[now][0])
{
ch[now][0]=len;
fa[len]=now;
break;
}
now=ch[now][0];
}
else
{
if(!ch[now][1])
{
ch[now][1]=len;
fa[len]=now;
break;
}
now=ch[now][1];
}
}
splay(len);
}
int findpre()
{
int x=ch[root][0];
while(ch[x][1])
x=ch[x][1];
return x;
}
int findsuc()
{
int x=ch[root][1];
while(ch[x][0])
x=ch[x][0];
return x;
}
void reset(int x)
{
ch[x][0]=ch[x][1]=fa[x]=0;
val[x].x=val[x].t=0;
}
void dele(int x)
{
splay(x);
if(!ch[x][0] || !ch[x][1])
{
root=ch[x][0]+ch[x][1];
fa[root]=0;
reset(x);
return;
}
int now=findsuc();
splay(now);ch[now][0]=ch[x][0];
fa[ch[now][0]]=now;
reset(x);
}
}tr;
int findf(int x)
{
return f[x]?f[x]=findf(f[x]):x;
}
void merge(int x,int y)
{
x=findf(x);y=findf(y);
if(x^y)
f[x]=y,cnt[y]+=cnt[x];
}
int main()
{
freopen("CERC2017B.in","r",stdin);
freopen("CERC2017B.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
int x,y;
scanf("%d%d",&x,&y);
a[i]=Tdata((x<<1)-1,(y<<1)-1,0);
}
scanf("%d",&m);
for(int i=1;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
a[i+n]=Tdata(x<<1,y<<1,i);
}
tot=n+m;
sort(a+1,a+tot+1,cmp);
for(int i=1;i<=tot;++i)
{
tr.insert(a[i].x,a[i].id);
if(a[i].id)
{
int suc=tr.findsuc();
//printf("%d\n",suc);
if(suc)
las[a[i].id]=tr.val[suc].t;
while(true)
{
int pre=tr.findpre();
if(!pre || tr.val[pre].tbreak;
tr.dele(pre);
}
}
else
{
int suc=tr.findsuc();
//printf("!%d\n",suc);
tr.dele(tr.root);
if(suc)
cnt[tr.val[suc].t]++;
}
}
for(int i=m;i;--i)
{
ans[i]=cnt[findf(i)];
if(las[i])
merge(i,las[i]);
}
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
return 0;
}
【总结】
其实想的方向是没有什么问题的,不过还是欠缺一点思路转换。
然后就是代码能力太弱了,一个splay两个小时才AC,太菜了。
省赛前要多打数据结构,提高代码能力!