【主席树/分块前缀】求矩阵中最少几个数之和≥给定数 洛谷P2468

粟粟需要通过在脚下放置书籍来摘苹果,每本书有不同的页数,每天只能在书架的特定区域取书,目标是找到摘苹果所需的最少书籍数量。问题涉及到数据结构和算法优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

幸福幼儿园B29班的粟粟是一个聪明机灵、乖巧可爱的小朋友,她的爱好是画画和读书,尤其喜欢Thomas H. Cormen的文章。粟粟家中有一个R行C列的巨型书架,书架的每一个位置都摆有一本书,上数第i行、左数第j列摆放的书有Pi,j页厚。

粟粟每天除了读书之外,还有一件必不可少的工作就是摘苹果,她每天必须摘取一个指定的苹果。粟粟家果树上的苹果有的高、有的低,但无论如何凭粟粟自己的个头都难以摘到。不过她发现,如果在脚下放上几本书,就可以够着苹果;她同时注意到,对于第i天指定的那个苹果,只要她脚下放置书的总页数之和不低于Hi,就一定能够摘到。

由于书架内的书过多,父母担心粟粟一天内就把所有书看完而耽误了上幼儿园,于是每天只允许粟粟在一个特定区域内拿书。这个区域是一个矩形,第i天给定区域的左上角是上数第x1i行的左数第y1i本书,右下角是上数第x2i行的左数第y2i本书。换句话说,粟粟在这一天,只能在这﹙x2i-x1i+1﹚×﹙y2i-y1i+1﹚本书中挑选若干本垫在脚下,摘取苹果。

粟粟每次取书时都能及时放回原位,并且她的书架不会再撤下书目或换上新书,摘苹果的任务会一直持续M天。给出每本书籍的页数和每天的区域限制及采摘要求,请你告诉粟粟,她每天至少拿取多少本书,就可以摘到当天指定的苹果。

输入输出格式

输入格式:

 

输入文件susu.in第一行是三个正整数R, C, M。

接下来是一个R行C列的矩阵,从上到下、从左向右依次给出了每本书的页数Pi,j。

接下来M行,第i行给出正整数x1i, y1i, x2i, y2i, Hi,表示第i天的指定区域是﹙x1i, y1i﹚与﹙x2i, y2i﹚间的矩形,总页数之和要求不低于Hi。

保证1≤x1i≤x2i≤R,1≤y1i≤y2i≤C。

 

输出格式:

 

输出文件susu.out有M行,第i行回答粟粟在第i天时为摘到苹果至少需要拿取多少本书。如果即使取走所有书都无法摘到苹果,则在该行输出“Poor QLW”(不含引号)。

 

输入输出样例

输入样例#1: 复制

5 5 7
14 15 9 26 53
58 9 7 9 32
38 46 26 43 38
32 7 9 50 28
8 41 9 7 17
1 2 5 3 139
3 1 5 5 399
3 3 4 5 91
4 1 4 1 33
1 3 5 4 185
3 3 4 3 23
3 1 3 3 108

输出样例#1: 复制

6
15
2
Poor QLW
9
1
3

输入样例#2: 复制

1 10 7
14 15 9 26 53 58 9 7 9 32
1 2 1 9 170
1 2 1 9 171
1 5 1 7 115
1 1 1 10 228
1 4 1 4 45704571
1 1 1 1 1
1 7 1 8 16

输出样例#2: 复制

6
7
3
10
Poor QLW
1
2

说明

【数据规模和约定】

对于10%的数据,满足R, C≤10;

对于20%的数据,满足R, C≤40;

对于50%的数据,满足R, C≤200,M≤200,000;

另有50%的数据,满足R=1,C≤500,000,M≤20,000;

对于100%的数据,满足1≤Pi,j≤1,000,1≤Hi≤2,000,000,000。

#include <bits/stdc++.h>
#define mid (l + r) / 2
using namespace std;
const int mn = 5e5 + 10;
const int inf = 5e5 + 10;

int a[mn], tr[mn];
struct node
{
	int sz, sum;
	int ls, rs;
} trie[1000 * 40 * 200 + 10];   // 两百棵主席树

int cnt;
void build(int l, int r, int &id)
{
	id = ++cnt;
	if (l == r)
		return;
	build(l, mid, trie[id].ls);
	build(mid + 1, r, trie[id].rs);
}

void Insert(int l, int r, int pre, int &now, int num)
{
	now = ++cnt;
	trie[now] = trie[pre];
	trie[now].sz++;
	trie[now].sum += num;

	if (l == r)
		return;

	if (num <= mid)
		Insert(l, mid, trie[pre].ls, trie[now].ls, num);
	else
		Insert(mid + 1, r, trie[pre].rs, trie[now].rs, num);
}

int x1, Y1, x2, y2;
int el[210], er[210];
int query(int l, int r, int h)
{
	if (l == r)
		return (h + l - 1) / l;     // 向上取整

	int rsz = 0, temp = 0;
	for (int i = x1; i <= x2; i++)
	{
		rsz += trie[trie[er[i]].rs].sz - trie[trie[el[i]].rs].sz;
		temp += trie[trie[er[i]].rs].sum - trie[trie[el[i]].rs].sum;
	}   // 每行第几大到第几大区间的增加值的和

	if (temp >= h)  // 右子树和足够 尽可能少的数 往右子树递归
	{
		for (int i = x1; i <= x2; i++)
		{
			el[i] = trie[el[i]].rs;
			er[i] = trie[er[i]].rs;
			// 左右界限分别对应于右子树的节点编号
		}
		return query(mid + 1, r, h);
	}
	else
	{
		for (int i = x1; i <= x2; i++)
		{
			el[i] = trie[el[i]].ls;
			er[i] = trie[er[i]].ls;
			// 左右界限分别对应于左子树的节点编号
		}
		return rsz + query(l, mid, h - temp);
	}
}

int main()
{
	build(1, 1000, tr[0]);

	int r, c, m;
	scanf("%d %d %d", &r, &c, &m);

	int temp;
	for (int i = 1; i <= r; i++)
	{
		for (int j = 1; j <= c; j++)    // 每行维护一棵主席树
		{
			scanf("%d", &temp);
			if (j == 1)
				Insert(1, 1000, tr[0], tr[(i - 1) * c + j], temp);  // 每行第一个点新创建主席树
			else
				Insert(1, 1000, tr[(i - 1) * c + j - 1], tr[(i - 1) * c + j], temp);    // 在同行前一个点的基础上
		}
	}

	while (m--)
	{
		int h;
		scanf("%d %d %d %d %d", &x1, &Y1, &x2, &y2, &h);

		int temp = 0;
		for (int i = x1; i <= x2; i++)
        {
            if (Y1 == 1)
                temp += trie[tr[(i - 1) * c + y2]].sum;
            else
                temp += trie[tr[(i - 1) * c + y2]].sum - trie[tr[(i - 1) * c + Y1 - 1]].sum;
        }   // 矩阵内数的总和

		if (temp < h)
			printf("Poor QLW\n");
		else    // 一定存在
		{
			for (int i = x1; i <= x2; i++)
			{
				if (Y1 == 1)
					el[i] = tr[0];
				else
					el[i] = tr[(i - 1) * c + Y1 - 1];
				er[i] = tr[(i - 1) * c + y2];
                // 每行的区间左右节点
			}
			printf("%d\n", query(1, 1000, h));
		}
	}

	return 0;
}
// 效华聚聚的
#include<bits/stdc++.h>
using namespace std;
int S[202][202][1005],t[202][202],a[202][1005];
int sum[50005][1002],c[500005];
int main()
{
    int n,m,Q;
    scanf("%d%d%d",&n,&m,&Q);
    if(n==1)
    {
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&c[i]);
            if(i%10==0)
            {
                int len=i/10;
                for(int j=1;j<=1000;j++) sum[len][j]=sum[len-1][j];
                for(int j=i;j>10*(len-1);j--) sum[len][c[j]]++;
            }
        }
        while(Q--)
        {
            int x1,y1,x2,y2,h;
            scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&h);
            int ans=0;
            for(int i=1000;i>0;i--)
            {
                int s=sum[y2/10][i]-sum[y1/10][i];
                for(int j=y2/10*10+1;j<=y2;j++) if(c[j]==i) s++;
                for(int j=y1/10*10+1;j<y1;j++) if(c[j]==i) s--;
                if(y1%10==0&&i==c[y1]) s++;
                if(i*s<=h)
                {
                    h-=i*s;
                    ans+=s;
                }
                else
                {
                    ans+=(h+i-1)/i;
                    h=0;
                }
                if(h==0) break;
            }
            if(h>0) printf("Poor QLW\n");
            else printf("%d\n",ans);
        }
    }
    else
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            scanf("%d",&t[i][j]);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                for(int k=1;k<=1000;k++)
                {
                    if(t[i][j]==k) a[j][k]=a[j-1][k]+1;
                    else a[j][k]=a[j-1][k];
                    S[i][j][k]=S[i-1][j][k]+a[j][k];
                }
            }
        }
        while(Q--)
        {
            int x1,y1,x2,y2,h;
            scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&h);
            int ans=0;
            for(int i=1000;i>0;i--)
            {
                int s=S[x2][y2][i]-S[x2][y1-1][i]-S[x1-1][y2][i]+S[x1-1][y1-1][i];
                if(i*s<=h)
                {
                    h-=i*s;
                    ans+=s;
                }
                else
                {
                    ans+=(h+i-1)/i;
                    h=0;
                }
                if(h==0) break;
            }
            if(h>0) printf("Poor QLW\n");
            else printf("%d\n",ans);
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值