zoukankan      html  css  js  c++  java
  • P3271 [JLOI2016]方 容斥+数学

    题意:

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

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

    分析:

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

    暴力做法:

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

    正解:

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

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

    img

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

    img

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

    image

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

    image

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

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

    image

    我们列出此时对(a)的限制$ale r,ale a+b-1,1le a,a+b-lle 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))的复杂度不优秀,其实(c)影响答案的值只在([l+1,r+1])范围内,因为小于(l+1)(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_4^2)次,统计答案的时候直接除掉就可以

    代码:

    #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
    ",(calc0()-calc1()+calc2()-calc3()+calc4()+mod)%mod);
    	}
    }
    
    signed main()
    {
    	zzc::work();
    	return 0;
     } 
    
  • 相关阅读:
    preprocess
    数组
    共用体
    动态内存管理函数
    C链表
    文件的定位与出错检查
    字符串读写函数
    C文件操作
    位运算
    爱好-超级IP:超级IP
  • 原文地址:https://www.cnblogs.com/youth518/p/13734005.html
Copyright © 2011-2022 走看看