数据结构学习之整体二分&&bzoj2738矩阵乘法题目分析

来源:互联网 发布:个人发卡网源码授权 编辑:程序博客网 时间:2024/06/05 05:33

数据结构学习之整体二分

前言:

整体二分是一个很神的东西,特点是代码短小精悍而且能解决一系列神数据结构的题目,他的来源是整体二分,思想从贡献下手,是一个非常好用的东东。

从二分答案到整体二分:

二分答案想必大家都熟悉,对于一个询问,若我们已知答案区间l,r,我们二分出一个答案mid;如果mid恰好符合答案则结束程序,否则判断答案落在(l,mid)还是(mid,r)继续二分下去。当然我们要求答案是可二分的。

二分答案单次的复杂度为O(f(n)logn)其中f(n)为判定答案合法性的复杂度

如果对于很多询问,显然是会爆炸的

所以,我们希望有一种算法可以支持多询问且复杂度为log。

因此有了整体二分

整体二分:整体?二分?

整体二分承袭了二分答案的思想,对答案进行二分,可是相对于二分答案,我们多了很多很多的询问,这个时候,我们不得不对询问区间进行处理。我们从询问的角度考虑二分答案,他其实把每次二分都把单个询问扔到了左区间或右区间中。那么我们现在搞的是大事情,我们要把整体进行二分,因此我们选择,把整个询问序列进行操作,把大于当前mid的询问扔到后面,小于mid的答案扔到前面进行,然后递归。来一段伪代码

Solve(Q, L, R) { //Q为待询问序列,L,R为答案区间 if(L > R) return;if(L == R) get_ans(Q); //将Q中询问答案置为Lint mid = L + R >> 1; //二分答案for(Q) { //枚举每个Q中的询问,判断每个询问属于的区间int value = get_f(cur); //计算当前询问答案Dived(cur) //根据当前答案划分其答案区间 } get_new(Q) //更新划分后的询问序列Solve(S0, L, mid); Solve(Q - S0, mid + 1, R) //分治处理 }

然而我们意识到了一个严重的问题,我们在get_f的函数时其复杂度往往是O(n)的,因此我们只是将二分答案合起来写了而已,时间复杂度并没有任何变化。

修改操作:神奇的贡献值

我们考虑二分答案的时候,我们验证合法性常常需要一定的限定性。拿K小数来说,假设答案是mid,那么我们只计算小于mid的数的个数。此时,我们发现,二分的答案每变化一次,之前计算过的所有mid值都可以计入答案,我们只需要计算比mid更大数的数值。换一个更专业的说法,就是(l,mid)的数据对答案的贡献值可以传递到(mid, r)中。所以,我们可以在划分区间的时候,我们首先加入mid之前的所有数据,并计算所有数据对答案的贡献值,同时划分数据。对于(l,mid)我们不做处理,对于(mid, r)我们更新他们的当前贡献值。之后照常划分就可以了。这样我们成功省去了冗余的计算。我们对之前的伪代码修改一下。

Solve(Q, L, R) { //Q为待询问序列,L,R为答案区间 if(L > R) return;if(L == R) get_ans(Q); //将Q中询问答案置为Lint mid = L + R >> 1; //二分答案for(A) Change(cur); //对于输入数据区间A,修改所有满足mid限制条件的A的贡献值,并用某个数据结构维护 for(Q) { //枚举每个Q中的询问,判断每个询问属于的区间int value = query(cur); //计算当前询问答案Dived(cur) //根据当前答案划分其答案区间 Update(cur) //更新询问的当前贡献值 } for(A) Rechange(cur); //还原数据A get_new(Q) //更新划分后的询问序列Solve(S0, L, mid); Solve(Q - S0, mid + 1, R) //分治处理 }

这个代码的关键部分是,query的复杂度变了,由于之前的修改操作,我们可以通过某个数据结构在比较短的时间内询问答案。因此总复杂度发生了质的改变。

顺便说一句,如果题目中有修改怎么破?很简单,我们将初始数据转化为修改操作,将所有询问和修改操作合并在一起,在计算贡献值的时候边修改边询问即可。

 

复杂度分析:

二分复杂度是logC,C为答案区间

剩下的是处理函数的复杂度,也就是修改+计算贡献值的操作复杂度,假设其复杂度为f(n)

总复杂度为O(f(n)logn)

总结:

整体二分主要是在二分答案的基础上,以贡献值的修改与传递为基础的离线数据处理利器,经典应用是K小数,常常可以起到简化数据结构的效果。

整体二分差不多学完啦!上题目!

 

2738: 矩阵乘法

Time Limit: 20 Sec  Memory Limit: 256 MB
Submit: 1647  Solved: 717
[Submit][Status][Discuss]

Description

  给你一个N*N的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第K小数。

Input

 
  第一行两个数N,Q,表示矩阵大小和询问组数;
  接下来N行N列一共N*N个数,表示这个矩阵;
  再接下来Q行每行5个数描述一个询问:x1,y1,x2,y2,k表示找到以(x1,y1)为左上角、以(x2,y2)为右下角的子矩形中的第K小数。

Output

  对于每组询问输出第K小的数。

Sample Input

2 2
2 1
3 4
1 2 1 2 1
1 1 2 2 3

Sample Output

1
3

HINT

  矩阵中数字是109以内的非负整数;

  20%的数据:N<=100,Q<=1000;

  40%的数据:N<=300,Q<=10000;

  60%的数据:N<=400,Q<=30000;

  100%的数据:N<=500,Q<=60000。

Source

二维树状数组+整体二分裸题。

每个数据的贡献值,就是小于等于答案的贡献为1否则为0, 每次添加小于等于mid的数据进入树状数组,询问和查询都是log2N的,把小于mid和大于mid的分为两个区间。

总复杂度是O(nlog2NlogC),下面是代码

#include<iostream>#include<cstdlib>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;const int N = 550;const int M = 66000;int read(){char ch = getchar(); int x = 0, f = 1;while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}return x * f;}struct data {int x, y, val;}a[N * N];struct Query {int x1, x2, y1, y2, K;} q[M];int lowbit(int x) {return (-x)&x;}bool cmp(data a, data b) {return a.val < b.val;}int n, m, top, tail, ans[M], id[M], tmp[M], sum[N][N];bool mark[M];int add(int x, int y, int add) {for(int i = x; i <= n; i += lowbit(i))for(int j = y; j <= n; j += lowbit(j))sum[i][j] += add;}int query(int x, int y) {int ret = 0;for(int i = x; i; i -= lowbit(i))for(int j = y; j; j -= lowbit(j))ret += sum[i][j];return ret;}int ask(int i) {int x1 = q[i].x1, x2 = q[i].x2, y1 = q[i].y1, y2 = q[i].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) return;int mid = L + R >> 1;while(a[tail + 1].val <= mid && tail < top) {++tail; add(a[tail].x, a[tail].y, 1);}while(a[tail].val > mid) {add(a[tail].x, a[tail].y, -1); --tail;}int cnt = 0;for(int i = l; i <= r; ++i) {if(ask(id[i]) >= q[id[i]].K) {mark[i] = true; ans[id[i]] = mid; ++cnt;}else mark[i] = false;}int l1 = l, l2 = l + cnt;for(int i = l;i <= r; ++i)if(mark[i]) tmp[l1++] = id[i];else tmp[l2++] = id[i];for(int i = l;i <= r; ++i) id[i] = tmp[i];solve(l, l1 - 1, L, mid); solve(l1, l2 - 1, mid + 1, R);}int main(){n = read(); m = read();int mx = 0;for(int i = 1;i <= n; ++i) for(int j = 1;j <= n; ++j) {a[++top].x = i; a[top].y = j; mx = max(a[top].val = read(), mx);}sort(a + 1, a + top + 1, cmp);for(int i = 1;i <= m; ++i) {q[i].x1 = read(); q[i].y1 = read(); q[i].x2 = read(); q[i].y2 = read(); q[i].K = read();}for(int i = 1;i <= m; ++i) id[i] = i;solve(1, m, 0, mx + 1);for(int i = 1;i <= m; ++i) printf("%d\n", ans[i]);return 0;}

参考博客:

啃论文

巨佬的介绍

原创粉丝点击