洛谷 P1527 [国家集训队]矩阵乘法 解题报告
P1527 [国家集训队]矩阵乘法
题目描述
给你一个\(N*N\)的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第\(K\)小数。
输入输出格式
输入格式:
第一行两个数\(N,Q\),表示矩阵大小和询问组数;
接下来\(N\)行\(N\)列一共\(N*N\)个数,表示这个矩阵;
再接下来\(Q\)行每行\(5\)个数描述一个询问:\(x1,y1,x2,y2,k\)表示找到以\((x1,y1)\)为左上角、以\((x2,y2)\)为右下角的子矩形中的第\(K\)小数。
输出格式:
对于每组询问输出第\(K\)小的数。
说明
矩阵中数字是\(10^9\)以内的非负整数;
\(20\%\)的数据:\(N<=100,Q<=1000\);
\(40\%\)的数据:\(N<=300,Q<=10000\);
\(60\%\)的数据:\(N<=400,Q<=30000\);
\(100\%\)的数据:\(N<=500,Q<=60000\)。
整体二分真有趣。
直接把整体二分里面的改成二维树状数组就可以了
复杂度\(O((N^2+M)\log10^9\log N^2)\)
Code:
#include <cstdio>
const int N=502;
const int M=310010;
const int inf=0x3f3f3f3f;
struct node{int op,a,b,c,d,k;}q[M],ql[M],qr[M];
int s[N][N],n,m,Q,ans[M];
void add(int x,int y,int d)
{
for(int i=x;i<=n;i+=i&-i)
for(int j=y;j<=n;j+=j&-j)
s[i][j]+=d;
}
int query(int x,int y)
{
int sum=0;
for(int i=x;i;i-=i&-i)
for(int j=y;j;j-=j&-j)
sum+=s[i][j];
return sum;
}
#define rep(i,a,b) for(int i=a;i<=b;i++)
void divide(int l,int r,int s,int t)
{
if(s>t)return;
if(l==r){rep(i,s,t)ans[q[i].op]=l;return;}
int mid=l+r>>1,lp=0,rp=0;
rep(i,s,t)
{
if(q[i].op)
{
int p=query(q[i].c,q[i].d)-query(q[i].c,q[i].b-1)
-query(q[i].a-1,q[i].d)+query(q[i].a-1,q[i].b-1);
if(q[i].k<=p) ql[++lp]=q[i];
else qr[++rp]=q[i],qr[rp].k-=p;
}
else
{
if(q[i].k<=mid) add(q[i].a,q[i].b,1),ql[++lp]=q[i];
else qr[++rp]=q[i];
}
}
rep(i,s,t) if(!q[i].op&&q[i].k<=mid) add(q[i].a,q[i].b,-1);
rep(i,s,s+lp-1) q[i]=ql[i+1-s];
rep(i,s+lp,t) q[i]=qr[i+1-s-lp];
divide(l,mid,s,s+lp-1),divide(mid+1,r,s+lp,t);
}
int main()
{
scanf("%d%d",&n,&Q);
rep(i,1,n) rep(j,1,n) ++m,scanf("%d",&q[m].k),q[m].a=i,q[m].b=j;
rep(i,1,Q) ++m,scanf("%d%d%d%d%d",&q[m].a,&q[m].b,&q[m].c,&q[m].d,&q[m].k),q[m].op=i;
divide(0,inf,1,m);
rep(i,1,Q) printf("%d\n",ans[i]);
return 0;
}
2018.11.1