【整体二分】[国家集训队]矩阵乘法

题目

题目描述
给你一个 n \times nn×n 的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第 kk 小数。

输入格式
第一行有两个整数,分别表示矩阵大小 nn 和询问组数 qq。

第 22 到第 (n + 1)(n+1) 行,每行 nn 个整数,表示这个矩阵。第 (i + 1)(i+1) 行的第 jj 个数表示矩阵第 ii 行第 jj 列的数 a_{i, j}a
i,j

接下来 qq 行,每行五个整数 x_1, y_1, x_2, y_2, kx
1

,y
1

,x
2

,y
2

,k,表示一组询问,要求找到以 (x_1, y_1)(x
1

,y
1

) 为左上角,(x_2, y_2)(x
2

,y
2

) 为右下角的子矩形中的第 kk 小数。

输出格式
对于每组询问,输出一行一个整数表示答案。

输入输出样例
输入 #1复制
2 2
2 1
3 4
1 2 1 2 1
1 1 2 2 3
输出 #1复制
1
3
说明/提示
数据规模与约定
对于 20%20% 的数据,保证 n \leq 100n≤100,q \leq 10^3q≤10
3

对于 40%40% 的数据,保证 n \leq 300n≤300,q \leq 10^4q≤10
4

对于 60%60% 的数据,保证 n \leq 400n≤400,q \leq 3 \times 10^4q≤3×10
4

对于 100%100% 的数据,保证 1 \leq n \leq 5001≤n≤500,1 \leq q \leq 6 \times 10^41≤q≤6×10
4
,0 \leq a_{i, j} \leq 10^90≤a
i,j

≤10
9

思路

这道题把序列查询区间第kk大搬到了矩阵上,但是仍然满足二分性质,所以我们还是可以整体二分

而我们沿用序列上的做法,把点和询问都离线下来,揉在一起整体二分

唯一不一样的地方就是需要二维树状数组

代码

#include<bits/stdc++.g>
const int N = 500;
const int M = 6e4;
const int N2 = 250000;
const int INF = 1e9;
using namespace std;
struct node
{
    int x1,y1,x2,y2,k,id;
}q[M + N2 + 5],q1[M + N2 + 5],q2[M + N2 + 5];
int n,m,a[N + 5][N + 5],ans[M + 5],c[N + 5][N + 5],cnt;
int lowbit(int x)
{
    return x & (-x);
}
void add(int x,int y,int s)
{
    for (int i = x;i <= n;i += lowbit(i))
        for (int j = y;j <= n;j += lowbit(j))
            c[i][j] += s;
}
int query(int x,int y)
{
    if (!x || !y)
        return 0;
    int ans = 0;
    for (int i = x;i;i -= lowbit(i))
        for (int j = y;j;j -= lowbit(j))
            ans += c[i][j];
    return ans;
}
int query_sum(int x1,int y1,int x2,int y2)
{
    return query(x2,y2) - query(x1 - 1,y2) - query(x2,y1 - 1) + query(x1 - 1,y1 - 1);
}
void solve(int l,int r,int L,int R)
{
    if (L > R)
        return;
    if (l == r)
    {
        for (int i = L;i <= R;i++)
            if (q[i].id)
                ans[q[i].id] = l;
        return;
    }
    int mid = l + r >> 1,cnt1 = 0,cnt2 = 0;
    for (int i = L;i <= R;i++)
        if (q[i].id == 0)
        {
            if (q[i].k <= mid)
            {
                add(q[i].x1,q[i].y1,1);
                q1[++cnt1] = q[i];
            }
            else
                q2[++cnt2] = q[i];
        }
        else
        {
            int x = query_sum(q[i].x1,q[i].y1,q[i].x2,q[i].y2);
            if (x >= q[i].k)
                q1[++cnt1] = q[i];
            else
            {
                q[i].k -= x;
                q2[++cnt2] = q[i];
            }
        }
    for (int i = 1;i <= cnt1;i++)
        if (q1[i].id == 0)
            add(q1[i].x1,q1[i].y1,-1);
    for (int i = L;i <= L + cnt1 - 1;i++)
        q[i] = q1[i - L + 1];
    for (int i = L + cnt1;i <= R;i++)
        q[i] = q2[i - L - cnt1 + 1];
    solve(l,mid,L,L + cnt1 - 1);
    solve(mid + 1,r,L + cnt1,R);
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= n;j++)
        {
            scanf("%d",&a[i][j]);
            q[++cnt] = (node){i,j,0,0,a[i][j],0};
        }
    for (int i = 1;i <= m;i++)
    {
        cnt++;
        scanf("%d%d%d%d%d",&q[cnt].x1,&q[cnt].y1,&q[cnt].x2,&q[cnt].y2,&q[cnt].k);
        q[cnt].id = i;
    }
    solve(0,INF,1,cnt);
    for (int i = 1;i <= m;i++)
        printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值