Luogu P2468 [SDOI2010]粟粟的书架
半道题:前缀和,矩阵
另外半道题:主席树
对于半道题我们开三维数组两个
sum[i][j][p]的值是以(1,1)为左上角以(i,j)为右下角的矩阵中 书的值>=p的和
num[i][j][p]的值是以(1,1)为左上角以(i,j)为右下角的矩阵中 书的值>=p的数量
二分查找满足要求的p
- ————————————预处理
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x;
scanf("%d",&x);
for(int p=1;p<=1000;p++)
{
sum[i][j][p]=sum[i-1][j][p]+sum[i][j-1][p]-sum[i-1][j-1][p];
num[i][j][p]=num[i-1][j][p]+num[i][j-1][p]-num[i-1][j-1][p];
if(x>=p)
sum[i][j][p]+=x,num[i][j][p]+=1;
}
}
}
参考pic1 x是橙色格子的值
[i][j][p]是红色格子的信息
显然红色格子=黄色+绿色+橙色-蓝色
pic 1 ^
- ————————————getsum和getnum
int getsum(int x)
{
return sum[cc][dd][x]-sum[aa-1][dd][x]-sum[cc][bb-1][x]+sum[aa-1][bb-1][x];
}
int getnum(int x)
{
return num[cc][dd][x]-num[aa-1][dd][x]-num[cc][bb-1][x]+num[aa-1][bb-1][x];
}
参考pic2 以(aa,bb)为左上角,以(cc,dd)为右下角的长方形是橙色的
显然 橙色= 蓝色-黄色-绿色+灰色
pic2^
对于另外半道题
主席树维护
和静态第k小差不多
不过不仅维护个数,还维护和
也是二分查找满足条件的p
然后主席树查找>p的书的和和个数
代码+注释
#include<cstdio>
#include<cstring>
int sum[210][210][1010],num[210][210][1010];
int aa,bb,cc,dd,goal,n,m,k;
int a[500500],root[500500];
int len=0;
struct nod1{int tot,s,lc,rc;}tr[10001000];
int getsum(int x)
{
return sum[cc][dd][x]-sum[aa-1][dd][x]-sum[cc][bb-1][x]+sum[aa-1][bb-1][x];
}
int getnum(int x)
{
return num[cc][dd][x]-num[aa-1][dd][x]-num[cc][bb-1][x]+num[aa-1][bb-1][x];
}
//参考pic2 以(aa,bb)为左上角,以(cc,dd)为右下角的长方形是橙色的
//显然 橙色= 蓝色-黄色-绿色+灰色
int work1()
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x;
scanf("%d",&x);
for(int p=1;p<=1000;p++)
{
sum[i][j][p]=sum[i-1][j][p]+sum[i][j-1][p]-sum[i-1][j-1][p];
num[i][j][p]=num[i-1][j][p]+num[i][j-1][p]-num[i-1][j-1][p];
if(x>=p)
sum[i][j][p]+=x,num[i][j][p]+=1;
//参考pic1 x是橙色格子的值
//[i][j][p]是红色格子的信息
//显然红色格子=黄色+绿色+橙色-蓝色
}
}
}
int l,r,ans;
for(int i=1;i<=k;i++)
{
l=1;r=1000;ans=-1;
scanf("%d %d %d %d %d",&aa,&bb,&cc,&dd,&goal);
while(l<=r)
{
int mid=(l+r)/2;
if(getsum(mid)>=goal)
{
ans=mid;l=mid+1;
}
else
r=mid-1;
}
if(ans==-1)printf("Poor QLW\n");
else printf("%d\n",getnum(ans)-((getsum(ans)-goal)/ans));
//!!!
//getnum(ans)是大于等于ans的书的数量
//getsum(asn)是大于等于ans的书加在一起的值
//可是!大于ans的书是一定取的
//但值=ans的书不一定全部取,我们要去掉冗余的
}
}
int work2do(int x,int y,int goal)
{
int l=1,r=1000,an=0;
x=root[x-1];y=root[y];
if(tr[y].tot-tr[x].tot<goal)return -1;
//全部也没有这么多
while(l<r)
{
int mid=(l+r)/2;
int g=tr[tr[y].rc].tot-tr[tr[x].rc].tot;
if(g<=goal)
{
//右边全部值也小于当前想要的goal
//那就先取了右边的吧
an+=tr[tr[y].rc].s-tr[tr[x].rc].s;
//ans加上右边的个数
x=tr[x].lc;y=tr[y].lc;
//向左边找
goal-=g;
//更新需要的goal
r=mid;
}
else
{
//什么都不取先,去左边找
x=tr[x].rc;y=tr[y].rc;
l=mid+1;
}
}
an+=(goal+l-1)/l;
//!!!
//因为保证了取得到目标值(不然已经return -1回家了)
//但你可能还没有取完到这个目标值
//因为你到了叶子节点就出来了
//出来后满足l==r==你在二分的"quq",
//即这个区间内大于“quq”的数加起来可以达到目标值
//值>“quq”的书我们都需要,可是并不是值等于”quq"的书全部都需要,所以我们出来再考虑
//值等于"quq" 的书中 取几本
return an;
}
void update(int &rt,int l,int r,int x)
{
if(rt==0)
{
len++;rt=len;
}
tr[rt].s++;
tr[rt].tot+=x;
if(l==r)return ;
int mid=(l+r)/2;
if(x<=mid)update(tr[rt].lc,l,mid,x);
else update(tr[rt].rc,mid+1,r,x);
}
int mix(int &x,int y)
{
if(x==0)
{
x=y;return 0;
}
if(y==0)return 0;
tr[x].tot+=tr[y].tot;
tr[x].s+=tr[y].s;
mix(tr[x].lc,tr[y].lc);
mix(tr[x].rc,tr[y].rc);
}
int work2()
{
for(int i=1;i<=m;i++)
{
scanf("%d",&a[i]);
update(root[i],1,1000,a[i]);
//每次插入以root[i]为根一条链,这条链维护i这个点的信息
mix(root[i],root[i-1]);
//将其与前i-1条链合并,这样root[i]为根的这条链维护的就是1~i的信息
}
for(int i=1;i<=k;i++)
{
scanf("%d %d %d %d %d",&aa,&bb,&cc,&dd,&goal);
int fff=work2do(bb,dd,goal);
if(fff==-1)printf("Poor QLW\n");
else
printf("%d\n",fff);
}
}
int main()
{
scanf("%d %d %d",&n,&m,&k);
if(n==1)work2();//主席树
else work1();//矩阵
}
终于AC这题了T-T(是一天还是两天了。。)