题意:给一个矩阵,求一个大小固定的子矩阵,使得该子矩阵里除去最大元素后的方差最小。
分析:这题可以通过动态规划求和方法用O(n^2)的复杂度算出方差,方差=(矩阵元素平方和-2*矩阵元素平均值*矩阵元素总和+矩阵元素个数*矩阵元素平均值的平方)/矩阵元素个数。矩阵元素平方和和总和都可以用容斥原理通过动态规划求出(降维压缩矩阵)。
难点在于求出任一子矩阵的最大元素,刚开始完全不会,想用二维线段树,但是查询复杂度是logn*logn,在100*300*300的查询量上估计会挂掉,而且时间常数较大。后来查询资料之后发现有种方法被称为RMQ算法,实际上就是查询区间的二进制压缩优化,类似于线段树,但是充分利用了数组区间覆盖的特点,将查询复杂度由线段树的logn降为O(1),但是预处理过程的复杂度不会降低。RMQ的具体思想可以百度一下,资料很多,不再细说,将它扩展为二维也比较简单,比较四个小矩阵块的最值即可。
#include
#define MAXN 302
int
m,n,map[MAXN][MAXN],sum[MAXN][MAXN],rsum[MAXN][MAXN],rmq[MAXN][MAXN][9][9];
double
sqsum[MAXN][MAXN],sqrsum[MAXN][MAXN];//平方和记录数组一定要double,因为90000*220*220,会在int下溢出(WA了一次)
int logn(int x)
{
int s=0;
while(x)
{
x>>=1;
if(x) s++;
}
return s;
}
int max(int a,int b)
{
return a>b?a:b;
}
int query(int x1,int y1,int a,int b)//通过对四个子矩阵的查询,获得最值
{
int t1=logn(a),t2=logn(b),x2=x1+a,y2=y1+b;
int
m1=rmq[x1][y1][t1][t2],m2=rmq[x1][y2-(1<
return max(max(max(m1,m2),m3),m4);
}
int main()
{
int i,j,k,h,t1,t2,q,a,b,sx,sy,t,c=0;
double min,avg,tsum,tsqsum,res;
while(~scanf("%d%d",&m,&n))
{
printf("Case %d:\n",++c);
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
{
scanf("%d",&map[i][j]);
rsum[i][j]=rsum[i-1][j]+map[i][j];
sqrsum[i][j]=sqrsum[i-1][j]+map[i][j]*map[i][j];
sum[i][j]=sum[i][j-1]+rsum[i][j];
sqsum[i][j]=sqsum[i][j-1]+sqrsum[i][j];//递推平方和和总和
}
}
t1=logn(m);
t2=logn(n);
for(i=1;i<=m;i++)
{
for(j=1;j<=n;j++)
rmq[i][j][0][0]=map[i][j];
}
for(i=0;i<=t1;i++)
{
for(j=0;j<=t2;j++)
{
if(!i&&!j) continue;
for(k=1;k+(1<
{
for(h=1;h+(1<
{
if(i==0)
rmq[k][h][i][j]=max(rmq[k][h][i][j-1],rmq[k][h+(1<
else
rmq[k][h][i][j]=max(rmq[k][h][i-1][j],rmq[k+(1<
}
}
}
}//二维rmq的四阶递推过程
scanf("%d",&q);
while(q--)
{
scanf("%d%d",&a,&b);
min=1e20;
for(i=1;i+a-1<=m;i++)
{
for(j=1;j+b-1<=n;j++)
{
t=query(i,j,a,b);
tsum=sum[i+a-1][j+b-1]-sum[i+a-1][j-1]-sum[i-1][j+b-1]+sum[i-1][j-1]-t;
tsqsum=sqsum[i+a-1][j+b-1]-sqsum[i+a-1][j-1]-sqsum[i-1][j+b-1]+sqsum[i-1][j-1]-t*t;
avg=tsum/(a*b-1);
res=(tsqsum-2*avg*tsum+(a*b-1)*avg*avg)/(a*b-1);
if(res
{
min=res;
sx=i;
sy=j;
}
}
}
printf("(%d, %d), %.2f\n",sx,sy,min);
}
}
return 0;
}