snoi省选模拟赛 day3t3 bzoj 2509 正方形

       给定M条平行于坐标轴的线段,问共形成了多少个正方形。

       所谓形成了一个正方形,是指该正方形的四条边,均可由给定线段的一部分拼接而成。


       60分暴力还打挂了的成了0分的辣鸡 ...

       由于是线段的问题,我们可以将这条线段的存在赋在它右边(下边)的点上,横竖分开处理出一个点向左/右/上/下延伸的最大距离为lef,rig,up,dow,注意处理右边与下面点的时候,因为点值赋在右下端,判断的是[i][j+1]或者[i+1][j]是否为1。

       接着处理出来了这个东西,由于能构成正方形的一定在同一条对角线上,我们可以通过枚举对角线,再枚举其对角线上之前的点,判断当前点的lef与up的min值,是否能包括其枚举的上一个点,以及枚举的上一个点的rig与dow值,能否覆盖当前点,如果都可以则是一组可行解了。枚举点n^2,枚举同一条对角线上的另外一个点o(n) 总复杂度n^3。

       那么如何优化呢,可以使用树状数组,具体操作如下

       对于每次枚举的时候,我们第一步查询它左上覆盖范围内,有多少加入了的点,即为这个点的答案。第二步将它加入到树状数组中,第三步在它超出了它右下的最大覆盖范围时,将它从树状数组中删去。

        这里我(题解)的做法是将每一个点拆成三个点,并建立结构体nod (flag,pos1,pos2) 。第一步操作记flag为-1,pos1设置为当前点的位置,pos2设置为当前点覆盖的最左上角,第二步操作记flag为0,pos1设置为当前点的位置,pos2因为只涉及插入所以随意,第三部操作记flag为1,pos1设置为当前点所覆盖的最右下角,pos2设置为当前点自己的位置(到pos1要删去的点)。那么首先按pos1排序,再按flag排序,执行上面的操作即可。注意顺序千万不能错。

        下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 1005
using namespace std;
typedef long long ll;
int read()  
{  
    char c;int sum=0,f=1;c=getchar();  
    while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}  
    while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}  
    return sum*f;  
}  
struct nod
{
	int flag,pos1,pos2;
	nod(int a,int b,int c)
	{
		flag=a;pos1=b;pos2=c;
	}
	nod(){}
}temp[maxn];
bool operator<(nod a,nod b)
{
	return a.pos1!=b.pos1 ? a.pos1<b.pos1 : a.flag<b.flag;
}
int n,m;
ll ans=0;
int dat[maxn];
int a[maxn][maxn],b[maxn][maxn];
int lef[maxn][maxn],rig[maxn][maxn],up[maxn][maxn],dow[maxn][maxn];
int lowbit(int now)
{
	return (now&(-now));
}
void add(int now,int val)
{
	for(int i=now;i<=n;i+=lowbit(i))
	dat[i]+=val;
}
int sum(int now)
{
	int res=0;
	for(int i=now;i>0;i-=lowbit(i))
	res+=dat[i];
	return res;
}
void solve(int x,int y)
{
	int tot=0;
	for(int i=0;i+x<=n && i+y<=n ;i++)
	{
		temp[++tot]=nod(-1,i+x,i+x-min(lef[i+x][i+y],up[i+x][i+y]));
		temp[++tot]=nod(0,i+x,0);
		temp[++tot]=nod(1,i+x+min(rig[i+x][i+y],dow[i+x][i+y]),i+x);
	}
	sort(temp+1,temp+1+tot);
	memset(dat,0,sizeof(dat));
	for(int i=1;i<=tot;i++)
	{
		switch(temp[i].flag)
		{
			case -1 : ans+=sum(n)-sum(temp[i].pos2-1);break;
			case 0  : add(temp[i].pos1,1);break;
			case 1  : add(temp[i].pos2,-1);break;
		}
	}
}
int main()
{
	n=read();m=read();n++;
	for(int i=1;i<=m;i++)
	{
		int x1,y1,x2,y2;
		x1=read();y1=read();x2=read();y2=read();
		x1++;y1++;x2++;y2++;
		if(x1==x2) for(int j=y1+1;j<=y2;j++) a[x1][j]=1;
		else for(int j=x1+1;j<=x2;j++) b[j][y1]=1;
	}
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(a[i][j]) lef[i][j]=lef[i][j-1]+1;
			if(b[i][j]) up[i][j]=up[i-1][j]+1;
		}
	}
	
	for(int i=n;i>=1;i--)
	{
		for(int j=n;j>=1;j--)
		{
			if(a[i][j+1]) rig[i][j]=rig[i][j+1]+1;
			if(b[i+1][j]) dow[i][j]=dow[i+1][j]+1;
		}
	}
	
	solve(1,1);
	for(int i=2;i<=n;i++)
	{
		solve(1,i);solve(i,1);
	}
	
	printf("%lld\n",ans);
}


       

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值