题意:给你一个R*C的棋盘,棋盘上的棋子会攻击,一个棋子会覆盖它所在的行,它所在的列,和它所在的从左上到右下的对角线,那么问这个棋盘上没有被覆盖的棋盘格子数。数据范围R,C,N<=50000
思路:直接做肯定会超时,所以需要一种nlogn的算法。我们一步一步来。
首先,我们肯定需要给被覆盖的行被覆盖的列做上标记,visx标记被覆盖的行,visy标记被覆盖的列,visd标记被覆盖的对角线
那么就是 visx[r]=1,visy[c]=1,visd[r-c+C]=1,给对角线这么标号避免了负的下标,即从右上角开始到左下角从1标到R-1+C,而且下面还会用到。
然后,如果我们不考虑被覆盖的对角线,那么没有被覆盖的格子数就是未被覆盖的行数*未被覆盖的列数,但是还有被覆盖的对角线。
下面,我们设为行的覆盖情况的集合为{r1,r2,r3....ri,.....rR}其中下标代表行号,值为1或0,代表是否被覆盖,1是没被覆盖,0是被覆盖,列也一样{c1,c2,c3....,cj......cC},我们再设对角线的{d1,d2,d3.........,dk........dx+y-1}但这里的值是这个对角线未被覆盖的值,
然后我们看,一开始我们没有考虑对角线,现在我们就要把对角线覆盖的部分从行列未覆盖的部分减去,那么我们就要知道在未考虑对角线覆盖情况下每个对角线没有被覆盖的格子数,然后根据visd把未被行列覆盖但被对角线覆盖的格子数减去。
那么每个对角线,那么标号为k的对角线的未被覆盖格子数是什么ΣΣ(ri*cj)(i-j+C=k)=dk。那么我们就发现了,ri*cj就相当于系数相乘,下标i-j+C就相当于指数相加,那么这时候就可以把它转化为多项式了,相当于两个多项式相乘,那么就要用到FFT了。
下面就是多项式系数赋值了。首先行的多项式就这么顺序赋值即可,又因为指数相加结果是i-j+C,那么行的指数就是i,列的指数就是拿j的系数做指数C-j的系数,这样相加就可以得到上面的求和公式的效果了。套FFT模板搞一下就行了,最后再结合visd去重即可。
思路参照鸟神的http://blog.csdn.net/u013368721/article/details/45367249,稍微解释了一下,非常感谢鸟神。
码字不容易,转载请注一下出处,包括我跟鸟神的。
码字不容易,转载请注一下出处,包括我跟鸟神的。
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <math.h>
#define clr(a,b) memset(a,b,sizeof(a));
using namespace std;
const int N = 500050;
const double PI = acos(-1.0);
struct Virt
{
double r, i;
Virt(double r = 0.0,double i = 0.0)
{
this->r = r;
this->i = i;
}
Virt operator + (const Virt &x)
{
return Virt(r + x.r, i + x.i);
}
Virt operator - (const Virt &x)
{
return Virt(r - x.r, i - x.i);
}
Virt operator * (const Virt &x)
{
return Virt(r * x.r - i * x.i, i * x.r + r * x.i);
}
};
//雷德算法--倒位序
void Rader(Virt F[], int len)
{
int j = len >> 1;
for(int i=1; i<len-1; i++)
{
if(i < j) swap(F[i], F[j]);
int k = len >> 1;
while(j >= k)
{
j -= k;
k >>= 1;
}
if(j < k) j += k;
}
}
//FFT实现
void FFT(Virt F[], int len, int on)
{
Rader(F, len);
for(int h=2; h<=len; h<<=1) //分治后计算长度为h的DFT
{
Virt wn(cos(-on*2*PI/h), sin(-on*2*PI/h)); //单位复根e^(2*PI/m)用欧拉公式展开
for(int j=0; j<len; j+=h)
{
Virt w(1,0); //旋转因子
for(int k=j; k<j+h/2; k++)
{
Virt u = F[k];
Virt t = w * F[k + h / 2];
F[k] = u + t; //蝴蝶合并操作
F[k + h / 2] = u - t;
w = w * wn; //更新旋转因子
}
}
}
if(on == -1)
for(int i=0; i<len; i++)
F[i].r /= len;
}
//求卷积
void Conv(Virt a[],Virt b[],int len)
{
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,n;
int visx[N],visy[N],visd[N<<1];
Virt va[N<<1],vb[N];
int len;
long long numx,numy;
long long answer;
void Init()
{
clr(visx,0);
clr(visy,0);
clr(visd,0);
numx=numy=0;
answer=0;
scanf("%d%d%d",&R,&C,&n);
int a,b;
for(int i=1;i<=n;i++){
scanf("%d%d",&a,&b);
visx[a]=1;
visy[b]=1;
visd[a-b+C]=1;
}
for(int i=1;i<=R;i++)
numx+=visx[i]==0;
for(int j=1;j<=C;j++)
numy+=visy[j]==0;
answer=(long long)numx*numy;
len=1;
while(len<R+C+1) len<<= 1;
for(int i=1;i<=R;i++)
va[i-1]=Virt(visx[i]==0,0.0);
for(int i=R;i<len;i++)
va[i]=Virt(0.0,0.0);
for(int i=1;i<=C;i++)
vb[C-i]=Virt(visy[i]==0,0.0);
for(int i=C;i<len;i++)
vb[i]=Virt(0.0,0.0);
}
int main()
{
int T;
int cas=0;
scanf("%d",&T);
while(T--)
{
Init();
Conv(va,vb,len);
for(int i=0; i<len; i++)
answer-=(long long )(va[i].r+0.5)*visd[i+1];//被对角线覆盖的去掉
printf ( "Case %d: %lld\n" , ++cas , answer ) ;
}
return 0;
}