P3271 [JLOI2016]方 容斥+数学

题意:

给定一个 n ∗ m n*m nm的矩阵,从中删去 k k k个顶点,求最后能形成多少个正方形

范围&性质: 1 ≤ n , m ≤ 1 0 6 , 1 ≤ k ≤ 2000 1\le n,m\le 10^6,1\le k\le 2000 1n,m106,1k2000,正方形可以是斜着的(边不一定与网格图上的边重合)

分析:

(下文所有图片均来源于其他julao博客)

暴力做法:

枚举,复杂度 O ( n m k ) O(nmk) O(nmk)直接去世

正解:

通过容斥简化运算,记 f ( i ) f(i) f(i)表示至少包含 i i i个被删除的点的正方形数目,最终的答案就是 f ( 0 ) − f ( 1 ) + f ( 2 ) − f ( 3 ) + f ( 4 ) f(0)-f(1)+f(2)-f(3)+f(4) f(0)f(1)+f(2)f(3)+f(4),那么问题转化成了如何求 f ( i ) f(i) f(i)

首先我们考虑对于一个被删除的点,它能形成的正方形有多少种,情况如下图所示

img

我们很容易观察出正方形可以分为两类,斜着的和正着的,但是斜着的正方形可以在大的正方形里被统计,RT

img

我们按照横纵坐标的差值,对斜着的正方形进行定义,如下图的正方形我们可以称其为 ( a , b ) (a,b) (a,b)正方形

image

那么对于一个被删除的点,它的属性可以用四个值表示分别为 ( u , d , l , r ) (u,d,l,r) (u,d,l,r)

image

我们先考虑 ( 0 , x ) (0,x) (0,x)正方形,也就是横平竖直的正方形,分象限考虑,这样的正方形的数目是 m i n ( l , d ) + m i n ( d , r ) + m i n ( u , r ) + m i n ( u , l ) min(l,d)+min(d,r)+min(u,r)+min(u,l) min(l,d)+min(d,r)+min(u,r)+min(u,l)

接下来考虑斜着的正方形,我们依旧分象限考虑,先考虑 ( l , r , d ) (l,r,d) (l,r,d)所在象限,正方形 ( a , b ) (a,b) (a,b)

image

我们列出此时对 a a a的限制$a\le r,a\le a+b-1,1\le a,a+b-l\le a $改为枚举a+b,转化成代码就是

    for(int c=2;c<=d&&c<=l+r;c++)
    {
        ans+=min(r,c-1)-max(1,c-l)+1;
    }

但是,单次 O ( n 2 ) O(n^2) O(n2)的复杂度不优秀,其实 c c c影响答案的值只在 [ l + 1 , r + 1 ] [l+1,r+1] [l+1,r+1]范围内,因为小于 l + 1 l+1 l+1 m i n min min会取1,大于$ r+1 时 时 min$会取r,在取值范围内是一次函数,所以可以对枚举进行优化,只枚举分界点

    int lim=min(l+r,h),res=0;
    int pos[3]={l+1,r+1,lim};
    sort(pos,pos+3);
	int cl=1,cr,vl,vr;
	for(int i=0;i<3;i++)
	{
		cr=pos[i];
		if(cr>lim) break;
		if(cr<2||cl==cr) continue;
		cl++;
		vl=min(r,cl-1)-max(cl-l,1)+1;
		vr=min(r,cr-1)-max(cr-l,1)+1;
		res=(res+(vr+vl)*(cr-cl+1)/2)%mod;
		cl=cr;
	}
	return res;

对于含两个以上的情况,枚举两个点,利用向量或者坐标等数学知识推出其他点的坐标

tip:对于含至少三个点的情况会重复计算 C 3 2 C_3^2 C32次,对于至少四个点的情况会重复计算 C 4 2 C_4^2 C42次,统计答案的时候直接除掉就可以

代码:

#include<bits/stdc++.h>
#define mk(x,y) make_pair(x,y)
using namespace std;

namespace zzc
{
	const int mod = 1e8+7;
	int n,m,k;
	set<pair<int,int> > p;
	
	bool check(int x,int y)
	{
		return x>=0&&x<=n&&y>=0&&y<=m;
	}
	
	int calc(int l,int r,int h)
	{
		int lim=min(l+r,h),res=0;
		int pos[3]={l+1,r+1,lim};
		sort(pos,pos+3);
		int cl=1,cr,vl,vr;
		for(int i=0;i<3;i++)
		{
			cr=pos[i];
			if(cr>lim) break;
			if(cr<2||cl==cr) continue;
			cl++;
			vl=min(r,cl-1)-max(cl-l,1)+1;
			vr=min(r,cr-1)-max(cr-l,1)+1;
			res=(res+(vr+vl)*(cr-cl+1)/2)%mod;
			cl=cr;
		}
		return res;
	}
	
	int calc0()
	{
		int res=0;
		for(int i=1;i<=min(n,m);i++)
		{
			res=(res+(long long)(m-i+1)*(n-i+1)%mod*i%mod)%mod;
		}
		return res;
	}
	
	int calc1()
	{
		int res=0;
		for(set<pair<int,int> >::iterator it=p.begin();it!=p.end();it++)
		{
			int x=it->first,y=it->second;
			res+=calc(x,n-x,y);
			res+=calc(x,n-x,m-y);
			res+=calc(m-y,y,x);
			res+=calc(m-y,y,n-x);
			res+=min(x,y);
			res+=min(x,m-y);
			res+=min(n-x,y);
			res+=min(n-x,m-y);
			res=(res+mod)%mod;
		}
		return res;
	}
	
	int calc2()
	{
		int res=0;
		for(set<pair<int,int> >::iterator it=p.begin();it!=p.end();it++)
		{
			for(set<pair<int,int> >::iterator jt=it;jt!=p.end();jt++)
			{
				if(it==jt) continue;
				int x1=it->first,y1=it->second,x2=jt->first,y2=jt->second;
				if(check(x1+y1-y2,y1-x1+x2)&&check(x2+y1-y2,y2-x1+x2)) res++;
				if(check(x1-y1+y2,y1+x1-x2)&&check(x2-y1+y2,y2+x1-x2)) res++;
				if((x1+x2+y1+y2)&1) continue;
				if(check((x1+x2+y1-y2)/2,(y1+y2-x2+x1)/2)&&check((x1+x2-y1+y2)/2,(y1+y2+x2-x1)/2)) res++;
			}
		}
		return res;
	}
	
	int calc3()
	{
		int res=0;
		for(set<pair<int,int> >::iterator it=p.begin();it!=p.end();it++)
		{
			for(set<pair<int,int> >::iterator jt=it;jt!=p.end();jt++)
			{
				if(it==jt) continue;
				int x1=it->first,y1=it->second,x2=jt->first,y2=jt->second;
				if(check(x1+y1-y2,y1-x1+x2)&&check(x2-y2+y1,y2+x2-x1))
				{
					if(p.count(mk(x1+y1-y2,y1-x1+x2))) ++res;
					if(p.count(mk(x2-y2+y1,y2+x2-x1))) ++res;
				}
				if(check(x1-y1+y2,y1+x1-x2)&&check(x2+y2-y1,y2-x2+x1))
				{
					if(p.count(mk(x1-y1+y2,y1+x1-x2))) ++res;
					if(p.count(mk(x2+y2-y1,y2-x2+x1))) ++res;
				}
				if(((y1+y2+x1+x2)&1)==0)
				{
					if(check((x1+x2+y2-y1)/2,(y1+y2-x2+x1)/2)&&check((x1+x2-y2+y1)/2,(y1+y2+x2-x1)/2))
					{
						if(p.count(mk((x1+x2+y2-y1)/2,(y1+y2-x2+x1)/2))) ++res;
						if(p.count(mk((x1+x2-y2+y1)/2,(y1+y2+x2-x1)/2))) ++res;
					}	
		    	}
	    	}
    	}
    	return res/3;
    }
    
    int calc4()
	{
		int res=0;
		for(set<pair<int,int> >::iterator it=p.begin();it!=p.end();++it)
		{
			for(set<pair<int,int> >::iterator jt=it;jt!=p.end();++jt)
			{
				if(it==jt)continue;
				int x1=it->first,y1=it->second,x2=jt->first,y2=jt->second;
				if(check(x1+y1-y2,y1-x1+x2)&&check(x2-y2+y1,y2+x2-x1))
				{
					if(p.count(mk(x1+y1-y2,y1-x1+x2)) && p.count(mk(x2-y2+y1,y2+x2-x1))) ++res;
				}
				if(check(x1-y1+y2,y1+x1-x2)&&check(x2+y2-y1,y2-x2+x1))
				{
					if(p.count(mk(x1-y1+y2,y1+x1-x2)) && p.count(mk(x2+y2-y1,y2-x2+x1))) ++res;
				}
				if(((y1+y2+x1+x2)&1)==0)
				{
					if(check((x1+x2+y2-y1)/2,(y1+y2-x2+x1)/2 && check((x1+x2-y2+y1)/2,(y1+y2+x2-x1)/2)))
					{
						if(p.count(mk((x1+x2+y2-y1)/2,(y1+y2-x2+x1)/2))&& p.count(mk((x1+x2-y2+y1)/2,(y1+y2+x2-x1)/2))) ++res;
					}	
				}
			}
		}
		return res/6;
    }
	
	
    void work()
	{
		int a,b;
		scanf("%d%d%d",&n,&m,&k);
		for(int i=1;i<=k;i++)
		{
			scanf("%d%d",&a,&b);
			p.insert(mk(a,b));
		}
		printf("%d\n",(calc0()-calc1()+calc2()-calc3()+calc4()+mod)%mod);
	}
}

signed main()
{
	zzc::work();
	return 0;
 } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值