题解 P6551 【[COCI2010-2011#2] CRNI】
【题解】Crni [P6551] [SP7884]
传送门:
【分析】
这是一道套路题,用到了很多关于矩阵的处理技巧,但找到解决方法后会发现它的思维难度其实并不高,主要是代码实现较困难,所以也可以视其为膜你题。
【前缀和的套路】
找子矩阵基本都会用到前缀和,常见的查询子矩阵可以直接容斥,例如维护二维树状数组时用到的方法:
设
如果要查询以
在预处理式子时需要从左上角一直递推到右下角,而稍复杂一点的需要统计多个方向(没错,就是此题了),即从最多
【预处理】
回到此题。
为方便处理,将矩阵中的黑点设为
对于所有的黑点,先预处理出
前缀和递推方向:左上 → 右下 。
矩阵前缀和意义:右下角在
前缀和递推方向:右下 → 左上 。
矩阵前缀和意义:左上角在
前缀和递推方向:右上 → 左下 。
矩阵前缀和意义:左下角在
前缀和递推方向:左下 → 右上 。
矩阵前缀和意义:右上角在
最后,此题细节较多,变量名没设好的话很容易搞混。
时间复杂度为:
【Code】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define Re register int
#define For(i,a,b) for(Re i=a;i<=b;++i)
#define Por(i,a,b) for(Re i=a;i>=b;--i)
#define print() for(Re i=1;i<=n;puts(""),++i)for(Re j=1;j<=n;++j)
using namespace std;
const int N=1003,P=10007;
int n,Q[N],A[N][N],H[N][N],SS[N][N];char ch[N];
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
int RD[N][N];
inline void get_RD(){//RD[i][j]: 以i,j为右下角的黑矩形个数(1,1)→(n,n)
memset(H,0,sizeof(H));
For(i,1,n)For(j,1,n)if(A[i][j])H[i][j]=H[i-1][j]+1;
// print()printf("%d ",H[i][j]);puts("");
For(i,1,n){
Re h=1,t=0;
For(j,1,n)if(!A[i][j])RD[i][j]=-1;
RD[i][Q[++t]=0]=-1;
For(j,1,n){
while(h<=t&&H[i][Q[t]]>=H[i][j])--t;
if(h<=t&&A[i][j])RD[i][j]=RD[i][Q[t]]+1+H[i][j]*(j-Q[t])-1;
Q[++t]=j;
}
For(j,1,n)if(RD[i][j]<0)RD[i][j]=0;
}
// print()printf("%d ",RD[i][j]);puts("");
}
int LU[N][N];
inline void get_LU(){//LU[i][j]: 以i,j为左上角的黑矩形个数(n,n)→(1,1)
memset(H,0,sizeof(H));
Por(i,n,1)Por(j,n,1)if(A[i][j])H[i][j]=H[i+1][j]+1;
// print()printf("%d ",H[i][j]);puts("");
Por(i,n,1){
Re h=1,t=0;
Por(j,n,1)if(!A[i][j])LU[i][j]=-1;
LU[i][Q[++t]=n+1]=-1;
Por(j,n,1){
while(h<=t&&H[i][Q[t]]>=H[i][j])--t;
if(h<=t&&A[i][j])LU[i][j]=LU[i][Q[t]]+1+H[i][j]*(Q[t]-j)-1;
Q[++t]=j;
}
Por(j,n,1)if(LU[i][j]<0)LU[i][j]=0;
}
// print()printf("%d ",LU[i][j]);puts("");
}
int LD[N][N];
inline void get_LD(){//LD[i][j]: 以i,j为左下角的黑矩形个数(1,n)→(n,1)
memset(H,0,sizeof(H));
For(i,1,n)Por(j,n,1)if(A[i][j])H[i][j]=H[i-1][j]+1;
// print()printf("%d ",H[i][j]);puts("");
For(i,1,n){
Re h=1,t=0;
Por(j,n,1)if(!A[i][j])LD[i][j]=-1;
LD[i][Q[++t]=n+1]=-1;
Por(j,n,1){
while(h<=t&&H[i][Q[t]]>=H[i][j])--t;
if(h<=t&&A[i][j])LD[i][j]=LD[i][Q[t]]+1+H[i][j]*(Q[t]-j)-1;
Q[++t]=j;
}
Por(j,n,1)if(LD[i][j]<0)LD[i][j]=0;
}
// print()printf("%d ",LD[i][j]);puts("");
}
int RU[N][N];
inline void get_RU(){//RU[i][j]: 以i,j为右上角的黑矩形个数(n,1)→(1,n)
memset(H,0,sizeof(H));
Por(i,n,1)Por(j,n,1)if(A[i][j])H[i][j]=H[i+1][j]+1;
// print()printf("%d ",H[i][j]);puts("");
Por(i,n,1){
Re h=1,t=0;
For(j,1,n)if(!A[i][j])RU[i][j]=-1;
RU[i][Q[++t]=0]=-1;
For(j,1,n){
while(h<=t&&H[i][Q[t]]>=H[i][j])--t;
if(h<=t&&A[i][j])RU[i][j]=RU[i][Q[t]]+1+H[i][j]*(j-Q[t])-1;
Q[++t]=j;
}
For(j,1,n)if(RU[i][j]<0)RU[i][j]=0;
}
// print()printf("%d ",RU[i][j]);puts("");
}
inline int U_D(){//加上-下
Re ans=0,S=0;
For(i,1,n){
For(j,1,n)(ans+=S*LU[i][j]%P)%=P;//用【左上角为(i,j)的矩阵LU】固定在辅助线下面
For(j,1,n)(S+=RD[i][j])%=P;//用【右下角为(i,j)的矩阵RD】求辅助线上边的总个数
}
return ans%P;
}
inline int L_R(){//加左-右
Re ans=0,S=0;
For(j,1,n){
For(i,1,n)(ans+=S*LU[i][j]%P)%=P;//用【左上角为(i,j)的矩阵LU】固定在辅助线右边
For(i,1,n)(S+=RD[i][j])%=P;//用【右下角为(i,j)的矩阵RD】求辅助线左边的总个数
}
return ans%P;
}
inline int LU_RD(){//减左上-右下
Re ans=0;memset(SS,0,sizeof(SS));
For(i,1,n-1)For(j,1,n-1){
SS[i][j]=((RD[i][j]+SS[i-1][j]+SS[i][j-1])%P-SS[i-1][j-1]+P)%P;
//十字线左上角的用【右下角为(i,j)的矩阵RD】求总和
(ans+=SS[i][j]*LU[i+1][j+1]%P)%=P;//用【左上角为(i,j)的矩阵LU】固定十字线的右下角
}
return ans;
}
inline int RU_LD(){//减右上-左下
Re ans=0;memset(SS,0,sizeof(SS));
For(i,1,n-1)Por(j,n,2){
SS[i][j]=((LD[i][j]+SS[i-1][j]+SS[i][j+1])%P-SS[i-1][j+1]+P)%P;
//十字线右上角的用【左下角为(i,j)的矩阵LD】求总和
(ans+=SS[i][j]*RU[i+1][j-1]%P)%=P;//用【右上角为(i,j)的矩阵RU】固定十字线的左下角
}
return ans;
}
int main(){
// freopen("crni.in","r",stdin);
// freopen("crni.out","w",stdout);
in(n);
For(i,1,n){
scanf("%s",ch+1);
For(j,1,n)A[i][j]=(ch[j]=='C');
}
get_RD(),get_LU(),get_LD(),get_RU();
printf("%d\n",((U_D()+L_R())%P-(LU_RD()+RU_LD())%P+P)%P);
// fclose(stdin);
// fclose(stdout);
return 0;
}