Gym101620B Buffalo Barricades

Link
首先拆一下坐标,原本\((x,y)\)格子的中心变成\((2x-1,2y-1)\),右上角变成\((2x,2y)\)
一个很直观的想法是先离线下来,然后计算所有点对操作的贡献。
考虑对于每个操作的点找到时间最早的能够包住这个点的操作,这样会形成一棵森林
具体而言就是把所有点按\(y\)坐标降序排序并扫描线,然后用set维护横坐标,加入一个操作点时删除在它左边的并且时间比它晚的点(这里我们往前扫,碰到时间比它早的点即可退出,因为再往左的可能被这个点删除的点一定会被这个时间比它早的点删除),然后如果它的右边还存在某个点,就让这个点做它的父亲。
然后我们计算出每个初始的点在哪个操作点的管辖范围内,然后把这个操作点在树上的权值加一。
那么最后,树上每个点的权值可以贡献到的点就是不经过比该点时间更早的操作就可以到达的该点的祖先。
那么我们可以按时间升序枚举所有操作,答案就是它当前的子树和,然后切掉它与它父亲的边。
为了方便,我们可以按时间倒序枚举所有操作,然后就只需要并查集维护了。

#include<set>
#include<cstdio>
#include<cctype>
#include<algorithm>
using pi=std::pair<int,int>;
const int N=300007;
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
int n,m,ans[N],size[N],fa[N],f[N];std::set<pi>s;
struct node{int x,y,id;}a[N*2];
int operator<(const node&a,const node&b){return a.y>b.y;}
int find(int x){return x==f[x]? x:f[x]=find(f[x]);}
int main()
{
    n=read();
    for(int i=1;i<=n;++i) a[i]={read()*2-1,read()*2-1,0};
    m=read();
    for(int i=1;i<=m;++i) a[i+n]={read()*2,read()*2,f[i]=i};
    std::sort(a+1,a+n+m+1);
    for(int i=1;i<=n+m;++i)
    {
	if(!a[i].id)
	{
	    auto it=s.lower_bound({a[i].x,0});
	    if(it!=s.end()) ++size[it->second];
	}
	else
	{
	    pi x(a[i].x,a[i].id);auto it=s.insert(x).first;
	    for(;it!=s.begin()&&prev(it)->second>a[i].id;s.erase(prev(it)));
	    if(next(it)!=s.end()) fa[a[i].id]=next(it)->second;
	}
    }
    for(int i=m,u,v;i;--i) if(ans[i]=size[find(i)],fa[i]) u=find(i),v=find(fa[i]),size[v]+=size[u],f[u]=v;
    for(int i=1;i<=m;++i) printf("%d\n",ans[i]);
}
posted @ 2020-03-24 20:50  Shiina_Mashiro  阅读(123)  评论(0编辑  收藏  举报