zoukankan      html  css  js  c++  java
  • [SDOI2010]粟粟的书架

    原题链接

    原题链接

    算法介绍

    整道题可以分成两部分来看。

    对于仅有一行的部分:

      整个矩阵退化为一个序列,整个问题就变成一个序列上的问题。

      简化题意:给定一个序列,每次询问在一段区间内取若干个数,其总和是否能够大于一个给定值。

    考虑如下做法:

      对于每次询问,二分一个权值t,判断区间内权值大于等于t的所有数之和是否大于等于h,最终的结果是使满足以下条件的t最大化:区间内权值大于等于t的所有数之和大于等于h。

      因为要求取的数的个数最少,所以显然尽量取大的数更好。

      考虑主席树维护值域区间内所有数的和(记为sum)以及数的个数(记为cnt)。对于每次询问,同时递归查询以root[r]和root[l-1]为根的两棵主席树,先计算两者右子树的sum之差,记为rsum,若rsum>=h,则递归查询右边值大于等于h的结果,否则递归查询左边值大于等于h-rsum的结果。查询左边时,需要将递归的结果加上rcnt(因为相当于已经将右子树中所有的数都算进去了,还不够h,所以还需左子树的h-rsum,所以总个数是左边需要的个数加上rcnt)。

      注意最后的递归边界。此时已经到达边界,说明我们找到了答案,权值为l=r,而权值为r的数可能不止一个,而我们只需要将此时剩余的h填满即可,所以此时返回的结果应是ceil(h/r),ceil为上取整,而ceil(h/r)=floor((h+r-1)/r),所以可以直接写成(h+r-1)/r。

      整个算法时间复杂度O(nlogn),空间复杂度O(nlogn)

    对于形态为矩阵的部分:

      对于每次询问给定的矩阵,同样可以考虑二分。二分一个权值t,最终使得满足如下条件的t最大化:矩阵内所有大于等于t的数的和大于等于h。

      对于矩阵内所有大于等于t的数的和,可以考虑预处理二维前缀和,并且在普通二维前缀和的基础上加一维k,表示矩阵中大于等于k的所有数的二维前缀和。即:val[i,j,k]表示从(1,1)到(i,j)这个矩阵中,所有大于等于k的数的前缀和。

      最后的边界处同样要注意细节处理,对于二分的结果t,在所有等于t的数中,我们只需要取一部分,使得这一部分数加上所有大于t的数的和大于等于h即可,而并不一定要取满所有的等于t的数。我们以sum(x1,y1,x2,y2,t)表示矩阵(x1,y1),(x2,y2)中所有大于等于t的数的和,最终我们要将大于等于t的所有数的个数,减去(sum(x1,y1,x2,y2,t)-h)/t,减去的这一部分就是达到h之后还多出来的部分可以去掉的等于t的数的个数。

      由于我们需要知道矩阵中大于等于t的数的个数,所以可以同样预处理一个二维前缀和数组:num[i,j,k],表示(1,1)到(i,j)这个矩阵中,所有大于等于k的数的个数。

      整个算法时间复杂度O(MAX_V*RC+M*log MAX_V),空间复杂度O(MAX_V*RC)

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N=5e5+10,M=210;
    struct Node{
    	int l,r;
    	int sum;
    	int cnt;
    	#define l(p) tree[p].l
    	#define r(p) tree[p].r
    	#define sum(p) tree[p].sum
    	#define cnt(p) tree[p].cnt
    }tree[N*22];int idx;
    int root[N];
    int R,C,m;
    int w[N];
    int a[M][M];
    int val[M][M][1010];
    int num[M][M][1010];
    
    int build(int l,int r)
    {
    	int p=++idx;
    	if(l==r)return p;
    	int mid=l+r>>1;
    	l(p)=build(l,mid);
    	r(p)=build(mid+1,r);
    	return p;
    }
    
    int insert(int now,int l,int r,int pos)
    {
    	int p=++idx;
    	tree[p]=tree[now];
    	if(l==r){sum(p)+=pos,cnt(p)+=1;return p;}
    	int mid=l+r>>1;
    	if(pos<=mid)l(p)=insert(l(now),l,mid,pos);
    	else r(p)=insert(r(now),mid+1,r,pos);
    	sum(p)=sum(l(p))+sum(r(p));
    	cnt(p)=cnt(l(p))+cnt(r(p));
    	return p;
    } 
    
    int query(int u,int v,int l,int r,int h)
    {
    	if(l==r)return (h+l-1)/l;
    	int rsum=sum(r(v))-sum(r(u));
    	int rcnt=cnt(r(v))-cnt(r(u));
    	int mid=l+r>>1;
    	if(h<=rsum)return query(r(u),r(v),mid+1,r,h);
    	else return query(l(u),l(v),l,mid,h-rsum)+rcnt;
    }
    
    void solve_segment()
    {
    	for(int i=1;i<=C;i++)scanf("%d",&w[i]);
    	
    	root[0]=build(1,1000);
    	for(int i=1;i<=C;i++)root[i]=insert(root[i-1],1,1000,w[i]);
    	
    	while(m--)
    	{
    		int x1,l,x2,r,h;
    		scanf("%d%d%d%d%d",&x1,&l,&x2,&r,&h);
    		if(sum(root[r])-sum(root[l-1])<h)
    		{
    			puts("Poor QLW");
    			continue;
    		}
    		int ans=query(root[l-1],root[r],1,1000,h);
    		printf("%d
    ",ans);
    	}
    }
    
    int get_sum(int x1,int y1,int x2,int y2,int k)
    {
    	return val[x2][y2][k]-val[x1-1][y2][k]-val[x2][y1-1][k]+val[x1-1][y1-1][k];
    }
    
    int get_cnt(int x1,int y1,int x2,int y2,int k)
    {
    	return num[x2][y2][k]-num[x1-1][y2][k]-num[x2][y1-1][k]+num[x1-1][y1-1][k];
    }
    
    void solve()
    {
    	int maxv=1;
    	for(int i=1;i<=R;i++)
    		for(int j=1;j<=C;j++)
    			scanf("%d",&a[i][j]),maxv=max(maxv,a[i][j]);
    	
    	for(int k=1;k<=maxv;k++)
    		for(int i=1;i<=R;i++)
    			for(int j=1;j<=C;j++)
    			{
    				val[i][j][k]=val[i-1][j][k]+val[i][j-1][k]-val[i-1][j-1][k]+(a[i][j]>=k?a[i][j]:0);
    				num[i][j][k]=num[i-1][j][k]+num[i][j-1][k]-num[i-1][j-1][k]+(a[i][j]>=k?1:0);
    			} 
    			
    	while(m--)
    	{
    		int x1,y1,x2,y2,h;
    		scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&h);
    		if(get_sum(x1,y1,x2,y2,1)<h)
    		{
    			puts("Poor QLW");
    			continue;
    		}
    		int l=1,r=maxv;
    		while(l<r)
    		{
    			int mid=(l+r+1)/2;
    			if(get_sum(x1,y1,x2,y2,mid)>=h)l=mid;
    			else r=mid-1;
    		}
    		int ans=get_cnt(x1,y1,x2,y2,r);
    		ans-=(get_sum(x1,y1,x2,y2,r)-h)/r;
    		printf("%d
    ",ans);
    	}
    }
    
    int main()
    {
    	scanf("%d%d%d",&R,&C,&m);
    	if(R==1)solve_segment();
    	else solve();
    	return 0;
    }
  • 相关阅读:
    轻松背后的N+疲惫——系统日志
    Wcf实现IServiceBehavior拓展机制
    一个迭代小算法,根据指定的个数对下标进行分组
    SqlServer 游标用法
    DataView RowFilter
    Asp.net Repeater 排序
    asp.net 导出Excel
    C# 导出Excel(csv )
    C# 上传图片
    C# 调用外部.exe文件
  • 原文地址:https://www.cnblogs.com/ninedream/p/13045603.html
Copyright © 2011-2022 走看看