题目大意就是给你一个R*C的棋盘,上面有超级兵,这种超级兵会攻击 同一行、同一列、同一主对角线的所有元素,现在给你N个超级兵的坐标,需要你求出有多少方块是不能被攻击到的(R,C,N<50000)
遇到这种计数问题就要联想到容斥(组合数学太重要了),由容斥原理:
被攻击的方块数=行被攻击的方块数+列被攻击的方块数+主对角线被攻击的方块数-同时被行、列攻击的方块数-同时被行、对角线攻击的方块数-同时被列、对角线攻击的方块数+同时被行、列、对角线攻击的方块数
因为行列都不会很大,所以我们用三个数组分别记录行、列、对角线上含有超级兵的情况(从1开始)。对于对角线,我们不妨从右上角开始编起,从1
-R+C-1
,那么对于任意一块<x,y>
,它所属的对角线为x-y+C
。
开始计数:
行被攻击的方块数和列被攻击的方块数,这个很简单,就是扫一遍数组,然后如果有超级兵就加上
主对角线被攻击的方块数:我们需要知道对于任何一条主对角线,在R*C
的棋盘里,它的主对角线有多长,根据推导,我们发现当对角线编号为1~C
的时候,对角线都是从第一行开始的,它的长度按道理来讲应该是随着编号的增加递增的,但是当长度达到一定的时候就会收到行数的限制,因此长度为min(i,R)
。可以理解为,i就是因为收到列数的限制的最大的长度,但是同时还需要收到行数的限制。当编号为C+1~R+C-1
的时候,此时长度总体来讲会递减,主要收到行数的限制,但是同样也会收到列数的限制,因此长度为min(R+C-i,C)
。得到长度以后我们就可以遍历一遍得到主对角线被攻击的方块数
同时被行、列攻击的方块数:这个很简单,就是总共被攻击的行数*被攻击的列数
同时被行、对角线攻击的方块数:我们只要知道对角线在行上的范围,然后再统计在这个范围内有多少行被攻击就可以了。为了快速得到一个范围内有多少行被攻击,我们可以用一个前缀数组,来计算某一个行区间内有多少被攻击。现在重点在于如何求对角线所占行的范围。同样的我们需要进行分类讨论:当序号为1~C
的时候,每一个对角线都是从第1行开始的,我们只需要加上上面求得的长度就能够得到最后一行应该为min(i,C)
。当序号为C+1~R+C-1
的时候,每一行是从i-C+1
开始的,到i-C+1+min(R+C-i,C)
结束。
同时被列、对角线攻击的方块数:分析同上,我们这里只讨论每一条对角线列的范围额:当序号为1~C
的时候,是C-i+1
到C-i+1+min(i,C)
,当序号为C+1~R+C-1
的时候,是1
到min(R+C-1,C)
同时被行、列、对角线攻击的方块数:这个可能就没有很简单了,我们需要求出所有既被行、列攻击,又被对角线攻击的块数。设X
行Y
列,则如果要同时被攻击,应该还存在一个对角线X-Y+C
,观察这个式子,我们有什么方法迅速得到吗?好多个数相加、好多个数相减,我们还需要得到所有的结果,而且还不能遍历(会超时),我们就应该联想到FFT,加减运算转化为多项式乘法运算后,使用FFT就能nlogn解决问题。那么这两个多项式应该怎么构造呢?行多项式我们不妨就设为r[i]*xi,那么列多项式应该为c[i]*xC-i,这样运算以后的结果就是xi-j+C,得到的对角线的系数就是这条对角线同时被行列攻击的块数(因为i,j是不同的,所以是这个对角线上的不同的块)。如果i-j+C
处恰好有一条对角线,接说明这些块需要加上。
可以看一哈我写的代码,应该还是比较清晰的。
AC代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<climits>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<cmath>using namespace std;
const int MAXN=1<<17;
const double PI=acos(-1.0);
typedef long long ll;int read()
{int x=0,sign=1; char c=getchar();while(c<'0' || c>'0') {if(c=='-') sign=-1; c=getchar();}while(c>='0' && c<='9'){x=x*10+c-'0'; c=getchar();}return x*sign;
}struct complex
{double r,i;complex(double _r=0,double _i=0):r(_r),i(_i){}complex operator +(const complex &b) {return complex(r+b.r,i+b.i);}complex operator -(const complex &b) {return complex(r-b.r,i-b.i);}complex operator *(const complex &b) {return complex(r*b.r-i*b.i,r*b.i+i*b.r);}
}A[MAXN],B[MAXN];void change(complex y[],int len)
{int i,j,k;for(i = 1, j = len/2;i < len-1;i++){if(i < j)swap(y[i],y[j]);k = len/2;while( j >= k){j -= k;k /= 2;}if(j < k)j += k;}
}void fft(complex y[],int len,int on)
{change(y,len);for(int h = 2;h <= len;h <<= 1){complex wn(cos(-on*2*PI/h),sin(-on*2*PI/h));for(int j = 0;j < len;j += h){complex w(1,0);for(int k = j;k < j+h/2;k++){complex u = y[k];complex t = w*y[k+h/2];y[k] = u+t;y[k+h/2] = u-t;w = w*wn;}}}if(on == -1)for(int i = 0;i < len;i++)y[i].r /= len;
}void FFT(int a[],int la,int b[],int lb)//la,lb分别是a,b数组的最高位+1
{int len=1; while(len<la+lb) len<<=1;for(int i=0;i<la;++i) A[i]=complex(a[i],0);for(int i=la;i<len;++i) A[i]=complex(0,0);for(int i=0;i<lb;++i) B[i]=complex(b[i],0);for(int i=lb;i<len;++i) B[i]=complex(0,0);fft(A,len,1); fft(B,len,1);for(int i=0;i<len;++i) A[i]=A[i]*B[i];fft(A,len,-1);
}int R,C,D,N;
int r[MAXN],c[MAXN],d[MAXN];
int sumr[MAXN],sumc[MAXN];//行列前缀和
ll ans;int main()
{//freopen("data.txt","w",stdout);int T;//T=read();scanf("%d",&T);for(int Case=1;Case<=T;++Case){//initans=0; sumr[0]=sumc[0]=0;for(int i=0;i<=R;i++) r[i]=0;for(int i=0;i<=C;i++) c[i]=0;for(int i=0;i<=D;i++) d[i]=0;//read //R=read(); C=read(); N=read();scanf("%d%d%d",&R,&C,&N);int u,v;while(N--){//u=read(); v=read();scanf("%d%d",&u,&v);r[u]=1; c[v]=1; d[u-v+C]=1;}//统计行列被攻击的块数,得到前缀和 for(int i=1;i<=R;++i){if(r[i]) ans+=C; sumr[i]=sumr[i-1]+r[i];}for(int i=1;i<=C;++i){if(c[i]) ans+=R; sumc[i]=sumc[i-1]+c[i];}//test//减去行列同时被攻击的块数ans-=sumr[R]*sumc[C]; //统计主对角线被攻击的块数//减去同时被行和对角线、列和对角线攻击的块数 //1~R-1+C X-Y+CD=R-1+C;for(int i=1;i<=D;++i){if(d[i]){if(i<=C)//对角线块数递增,行数限制 {int dr1=0,dr2=min(i,R); //行数始末 int dc1=C-i,dc2=dc1+dr2;//列数始末 ans+=dr2;ans-=sumr[dr2]-sumr[dr1];ans-=sumc[dc2]-sumc[dc1];}else//对角线块数递减,列数限制 {int dc1=0,dc2=min(R+C-i,C);//列数始末 int dr1=i-C,dr2=dr1+dc2; //行数始末ans+=min(R+C-i,C);ans-=sumr[dr2]-sumr[dr1];ans-=sumc[dc2]-sumc[dc1];}}}//加上同时被行列对角线攻击的方块reverse(c,c+C+1);FFT(r,R+1,c,C+1);for(int i=1;i<=D;++i){if(d[i]){ans+=(ll)(A[i].r+0.5); }}printf("Case %d: %lld\n",Case,(ll)R*C-ans);//if(Case!=T) printf("\n");}return 0;
}