TwoLLogo
Task:
给定一个
n∗m
的平面图,由’.’与’#’构成,现要在’.’处画出两个
L
形图案,要求两个
Solution:
一个
L
由四个数字坐标决定,因此最朴素的做法即是枚举两个
bool judge(int x1,int y1,int l1,int r1,int x,int y){
if(x==l1){
if(y>=y1&&y<=r1)return true;
}
if(y==y1){
if(x>=x1&&x<=l1)return true;
}
return false;
}
struct P30{
void solve(){
ll ans=0;
for(int x1=1;x1<=n;x1++)
for(int y1=1;y1<=m;y1++){
if(str[x1][y1]=='#')continue;
for(int l1=x1+1;l1<=n;l1++){
if(str[l1][y1]=='#')break;
for(int r1=y1+1;r1<=m;r1++){
if(str[l1][r1]=='#')break;
for(int x2=1;x2<=n;x2++)
for(int y2=1;y2<=m;y2++){
if(str[x2][y2]=='#'||judge(x1,y1,l1,r1,x2,y2))continue;
for(int l2=x2+1;l2<=n;l2++){
if(str[l2][y2]=='#'||judge(x1,y1,l1,r1,l2,y2))break;
for(int r2=y2+1;r2<=m;r2++){
if(str[l2][r2]=='#'||judge(x1,y1,l1,r1,l2,r2))break;
ans++;
}
}
}
}
}
}
cout<<(ans/2)%P<<endl;
}
}P30;
首先,显然在枚举的过程中,对于
L
的两条边的长度是不必要在循环内检验的,可以直接用
定义
right[i][j]
表示从
(i,j)
点向右拓展能够拓展的长度,
up[i][j]
表示从
(i,j)
点向上拓展能够拓展的长度。
因此我们枚举
L
的拐点坐标,会有以下三种情况:
我们设第一个
- 对于第一种,只有标红的线段是限定的,其余线段无关,因此
tot=up[x1][y1]∗right[x1][y1]∗right[x2][y1]∗min(up[x2][y1],x2−x1−1);
- 对于第二种,所有线段都无限制,因此
tot=up[x1][y1]∗up[x2][y2]∗right[x1][y1]∗right[x2][y2]
- 对于第三种,第一个
L
向上的与第二个
L 向右的无关,而交点虚线部分的处理就比较麻烦了。我们可以分情况讨论:
- 交点处给第一个
L
:
tot=right[x1][y1]∗min(up[x2][y2],x2−x1−1) - 交点处给第二个
L
,第二个
L 向上的边在红色段已经在前一种方案的统计中算过,因此: tot=(up[x2][y2]−min(up[x2][y2],x2−x1−1))∗min(right[x1][y1],y2−y1−1)
- 交点处给第一个
L
:
于是就写出了 O(n4) 的代码,期望得分80分。
void Init(){
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++){
if(str[i][j]=='#')continue;
if(i==1||str[i-1][j]=='#')up[i][j]=0;
else up[i][j]=up[i-1][j]+1;
}
for(int i=1;i<=n;i++)
for(int j=m;j>=1;j--){
if(str[i][j]=='#')continue;
if(j==m||str[i][j+1]=='#')right[i][j]=0;
else right[i][j]=right[i][j+1]+1;
}
}
struct P80{
ll calc(int x1,int y1,int x2,int y2){//y1<y2
if(x1==x2)return 1LL*Up[x1][y1]*Up[x2][y2]*min(Right[x1][y1],y2-y1-1)*Right[x2][y2];
if(x1>x2)return 1LL*Up[x1][y1]*Up[x2][y2]*Right[x1][y1]*Right[x2][y2];
else{
int t=min(Up[x2][y2],x2-x1-1);
return 1LL*Up[x1][y1]*Right[x2][y2]*(1LL*Right[x1][y1]*t+1LL*(Up[x2][y2]-t)*min(Right[x1][y1],y2-y1-1));
}
}
void solve(){
ll ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(str[i][j]=='#')continue;
for(int k=j+1;k<=m;k++)
if(str[i][k]=='#')break;
else Right[i][j]++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(str[i][j]=='#')continue;
for(int k=i-1;k>=1;k--)
if(str[k][j]=='#')break;
else Up[i][j]++;
}
for(int y1=1;y1<=m;y1++)
for(int x1=1;x1<=n;x1++){
if(str[x1][y1]=='#')continue;
//y2==y1 judge
for(int x2=x1+1;x2<=n;x2++){
if(str[x2][y1]=='#')continue;
ans+=1LL*Up[x1][y1]*Right[x1][y1]*Right[x2][y1]*min(Up[x2][y1],x2-x1-1);
}
for(int y2=y1+1;y2<=m;y2++)
for(int x2=1;x2<=n;x2++){
if(str[x2][y2]=='#')continue;
ans+=calc(x1,y1,x2,y2);
}
}
cout<<ans%P<<endl;
}
}P80;
其实标程的复杂度就是
O(n4)
,然而 KyleYoung 大神写出了
O(n2)
的解法…Orz
凭借“正难则反”的指导思想,我们可以先算出总方案数,再减去不符合的方案数。
同样的,对于不符合的方案数,也是三种情况。我们可以选择一个特殊点来枚举,即图中两个
L
的交叉点。设此点坐标为
首先我们与处理出
left
数组与
down
数组。
left[i][j]
表示
(i,j)
左边各点向上延伸的方案数之和,
down[i][j]
表示
(i,j)
下边向右延伸的方案数之和。
- 对于第一种,第一个 L 的右边范围为与第二个
- 对于第二种: tot=right[i][j]∗up[i][j]∗left[i][j]∗(right[i][j]+1)
- 对于第三种: tot=right[i][j]∗up[i][j]∗down[i][j]∗(up[i][j]+1)
于是就解决了这道题,复杂度 O(n2)
#include<stdio.h>
#define P 1000000007
#define M 1005
char str[M][M];
int up[M][M],down[M][M],left[M][M],right[M][M];
int n,m;
void Init(){
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++){
if(str[i][j]=='#')continue;
if(i==1||str[i-1][j]=='#')up[i][j]=0;
else up[i][j]=up[i-1][j]+1;
}
for(int i=1;i<=n;i++)
for(int j=m;j>=1;j--){
if(str[i][j]=='#')continue;
if(j==m||str[i][j+1]=='#')right[i][j]=0;
else right[i][j]=right[i][j+1]+1;
}
for(int i=1;i<=n;i++)
for(int j=2;j<=m;j++)
if(str[i][j]!='#')left[i][j]=left[i][j-1]+up[i][j-1];
for(int j=1;j<=m;j++)
for(int i=n-1;i>=2;i--)
if(str[i][j]!='#')down[i][j]=down[i+1][j]+right[i+1][j];
}
int total(){
int sum=0,res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(str[i][j]!='#'){
int t=1LL*up[i][j]*right[i][j]%P;
res=(res+1LL*t*sum)%P;
sum=(sum+t)%P;
}
return res;
}
int calc1(){
int res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(str[i][j]!='#')res=(res+1LL*(up[i][j]+1)*down[i][j]%P*(right[i][j]+1)%P*left[i][j]%P)%P;
return res;
}
int calc2(){
int res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)//横向
if(str[i][j]!='#')
res=(res+1LL*right[i][j]*up[i][j]%P*left[i][j]%P*(right[i][j]+1)%P)%P;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)//竖直
if(str[i][j]!='#')res=(res+1LL*right[i][j]*up[i][j]%P*down[i][j]%P*(up[i][j]+1)%P)%P;
return res;
}
int doit(int x){
return (x%P+P)%P;
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",str[i]+1);
Init();
printf("%d\n",doit(doit(total()-calc1())-calc2()));
return 0;
}