P2468 [SDOI2010]粟粟的书架 & zloj 练习18 B

written on 2022-05-16

其实这题还是挺显然的,考试的时候因为题面太长,于是选择性跳过,然后死磕C题然而爆零,这题就留了15min,于是挂了,十分悲哀,当然,也有收获,对主席树上二分的理解更加深入了。

刚看到题目时,最引人注目的就是数据范围了。题目被分为两个独立的部分,感觉后50%更加显然,就是主席树。


后50%:

权值主席树上二分,这是典型的操作,但是我被“主席树维护的是前缀和”这样的过往做题经验蒙蔽了双眼。

事实上,主席树上的操作(同线段树)是以子树为单位处理的,要尽量逼近右端,那就直接判断右子树是否满足条件就行了。经验就是:主席树(权值线段树)不是一个单纯的维护前缀和的东西,在上面二分,可以有前缀和,当然也可以有后缀和,本质上就是对左右子树的查询罢了。


前50%:

其实是一种卡常做法,思路很简单,用二维前缀和套一个后缀和,然后询问时二分即可。这样的思路来源主要是因为 p 的范围极小,只有1000,然后就能卡过了。


细节:

  • 如果是不能摘到苹果的情况,要先判掉。

  • 在询问过程中,因为已经排除了不满足的情况,于是一定有解,注意区分 剩下的数能否被算出的最小数整除 的两种情况。


Code

#include<bits/stdc++.h>
#define N 1005
#define M 500005
using namespace std;
bool f1;
typedef long long ll;
struct Seg
{
	int tot,ls[M*32],rs[M*32],siz[M*32];
	ll val[M*32];
	//¿Õ¼äÓ¦¿ªµ½c*32
	//²åÈëc¸öµã£¬Ã¿´Î32±¶¿Õ¼ä 
	int build(int l,int r)
	{
		int p=++tot;
		ls[p]=rs[p]=val[p]=siz[p]=0;
		if(l==r) return p;
		int mid=l+r>>1;
		ls[p]=build(l,mid),rs[p]=build(mid+1,r);
		return p;
	}
	int New(int lst,int l,int r,int x)
	{
		int p=++tot;
		ls[p]=ls[lst],rs[p]=rs[lst],val[p]=val[lst],siz[p]=siz[lst];
		if(l==r)
		{
			val[p]+=x;
			siz[p]++;
			return p;
		}
		int mid=l+r>>1;
		if(x<=mid) ls[p]=New(ls[p],l,mid,x);
		else rs[p]=New(rs[p],mid+1,r,x);
		val[p]=val[ls[p]]+val[rs[p]];
		siz[p]=siz[ls[p]]+siz[rs[p]];
		return p;
	}
	int ask(int x,int y,int l,int r,ll k)
	{
		if(l==r)
		{
			if(k%l) return k/l+1;
			return k/l;
		}
		int mid=l+r>>1;
		ll num=val[rs[x]]-val[rs[y]];
		if(num>=k) return ask(rs[x],rs[y],mid+1,r,k);
		else return siz[rs[x]]-siz[rs[y]]+ask(ls[x],ls[y],l,mid,k-num);
	}
}t1;
int r,c,m;
int rt[M];
void solve1()
{
	int a[M];
	for(int i=1;i<=c;i++) scanf("%d",&a[i]);
	rt[0]=t1.build(1,N);
	for(int i=1;i<=c;i++) rt[i]=t1.New(rt[i-1],1,N,a[i]);
	for(int i=1;i<=m;i++)
	{
		int tx1,ty1,tx2,ty2,th;
		scanf("%d%d%d%d%d",&tx1,&ty1,&tx2,&ty2,&th);
		if(t1.val[rt[ty2]]-t1.val[rt[ty1-1]]<th) printf("Poor QLW\n");
		else printf("%d\n",t1.ask(rt[ty2],rt[ty1-1],1,N,1ll*th));
	}
}
int s[1005][205][205],siz[1005][205][205];
void solve2()
{
	int a[205][205];
	for(int i=1;i<=r;i++) for(int j=1;j<=c;j++) scanf("%d",&a[i][j]);
	for(int p=1;p<=1000;p++)
		for(int i=1;i<=r;i++) for(int j=1;j<=c;j++)
				{
					s[p][i][j]=(a[i][j]==p)*p+s[p][i-1][j]+s[p][i][j-1]-s[p][i-1][j-1];
					siz[p][i][j]=(a[i][j]==p)+siz[p][i-1][j]+siz[p][i][j-1]-siz[p][i-1][j-1];
				}
	for(int p=999;p>=1;p--)
		for(int i=1;i<=r;i++) for(int j=1;j<=c;j++) s[p][i][j]+=s[p+1][i][j],siz[p][i][j]+=siz[p+1][i][j];
	while(m--)
	{
		int tx1,ty1,tx2,ty2,th;
		scanf("%d%d%d%d%d",&tx1,&ty1,&tx2,&ty2,&th);
		tx1--,ty1--;
		if(s[1][tx2][ty2]-s[1][tx2][ty1]-s[1][tx1][ty2]+s[1][tx1][ty1]<th)
		{
			printf("Poor QLW\n");
			continue;
		}
		int l=1,r=1000,res=0;
		while(l<=r)
		{
			int mid=l+r>>1;
			if(s[mid][tx2][ty2]-s[mid][tx2][ty1]-s[mid][tx1][ty2]+s[mid][tx1][ty1]>=th) res=mid,l=mid+1;
			else r=mid-1;
		}
		int num=s[res+1][tx2][ty2]-s[res+1][tx1][ty2]-s[res+1][tx2][ty1]+s[res+1][tx1][ty1],ans;
		if((th-num)%res) ans=(th-num)/res+1;
		else ans=(th-num)/res;
		printf("%d\n",ans+siz[res+1][tx2][ty2]-siz[res+1][tx1][ty2]-siz[res+1][tx2][ty1]+siz[res+1][tx1][ty1]);
	}
}
bool f2;
int main()
{
//	printf("%d",(&f2-&f1)/1024/1024);
	scanf("%d%d%d",&r,&c,&m);
	if(r==1) solve1();
	else solve2();
}

题外话:听金钩大佬说,这样的内存估算方法竟然没什么用。。

但是我仍然相信cpp

posted @   Freshair_qprt  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示