最小生成树 找最小值 区间&运算修改
程序员文章站
2022-05-13 22:03:32
...
Read problems statements in Mandarin Chinese, Russian and Vietnamese as well.
You are given an array of N integers. You should support the following queries on this array.
- 0 L R : Find the minimum integer in the range AL, AL+1, ..., AR.
- 1 L R X : You should apply the assignment A[i] = A[i] & X, for all indices i in range [L, R], where & denotes bitwise AND operation.
Input
First line of the input contains two space separated integers N and Q.
Second line contains N integer numbers denoting array A.
In the next Q lines, each contain one of the queries described above.
Output
For each query of the type 0, output a single line containing the answer of the query.
Constraints
- 1 ≤ N, Q ≤ 105
- 1 ≤ Ai, X ≤ 109
- 1 ≤ L ≤ R ≤ N
Example
Input: 5 5 1 5 2 3 4 0 2 5 1 1 5 6 0 2 2 1 2 5 3 0 1 3 Output: 2 4 0
****为什么要引用线段树的理由需要想清楚,因为数据量太大,区间更新会超时,再区间找出最小值会超时!!!!这才是引用线段树的重要理由,因此下面利用 | (或运算)来寻找区间是否要修改的原因就是,如果对于每一个值都要去修改,那么一定会超时,所以才需要利用.st来保存区间是否与x的&运算有效,如果无效即直接返回,不需要递归到区间每一个值,那么时间复杂度将会大大减少,如果有效,那么找出需要修改的区间,再进一步递归找出需要修改的值。
综上所诉,需要利用.st的原因就是来减少时间复杂度的——这个问题我想了很久,如果把
if(a<=l&&r<=b)
{
int t=m[o].st & x;
if(t==m[o].st) return ;
}
注释掉,那么直接TLE.
找最小值利用线段树不难,难的在于对&运算理解这题目是利用归并然后与x值对比,如果可以修改,则继续找到最后的可以修改的区间,这样说可能会有点抽象
首先我们可以发现&运算是不可逆的
就是说 假设要&的这个x 某一位是0
他对一个区间的每一个数的这一位进行了&0的操作 那么这一位就永远是0了
这样我们可以发现 一个数真正有意义被&的次数其实最多是含有的1的个数次
如果这个x对这个区间有效 也就是说可以使得1减少那么就暴力递归搞 (线段树)
如何记录x对这个区间有效呢 我们只要记录这个区间 还有哪些位是1就好了
引用别人的一段话#include <iostream>
#include <algorithm>
#include <cstdio>
#define L o<<1
#define R o<<1|1
using namespace std;
struct node
{
long long x;
int l,r;
long long st;//此题为有条件更新
}m[4000000];
void pushup(int o)
{
m[o].x=min(m[L].x,m[R].x);
m[o].st=m[L].st | m[R].st;//与运算,将这个区间里所有数的二进制位数是1的直接取到一起,到时候判断x对l-r区间否有效
//如果无效则返回,如果有效则更新
}
void build(int o,int l,int r)
{
m[o].l=l;
m[o].r=r;
if(l==r)
{
scanf("%lld",&m[o].x);
m[o].st=m[o].x;
return ;
}
int mid=(l+r)/2;
build(L,l,mid);
build(R,mid+1,r);
pushup(o);
}
long long query(int o,int l,int r,int a,int b)
{
if(a<=l&&r<=b)
{
return m[o].x;
}
int mid=(l+r)/2;
long long t=100000000001;
if(a<=mid)t=min(t,query(L,l,mid,a,b));
if(b>mid) t=min(t,query(R,mid+1,r,a,b));
return t;
}
void update(int o,int l,int r,int a,int b,int x)
{
if(a<=l&&r<=b)//像判断最大值一样,看l-r区间是否在a-b区间里
{
int t=m[o].st & x;
if(t==m[o].st) return ;
}
if(l==r)
{
m[o].x=m[o].x & x;
m[o].st=m[o].x;
return ;
}
int mid=(l+r)/2;
if(a<=mid) update(L,l,mid,a,b,x);
if(b>mid) update(R,mid+1,r,a,b,x);
pushup(o);
}
int main()
{
int n,m,i,j,k;
while(~scanf("%d%d",&n,&m))
{
build(1,1,n);
while(m--)
{
scanf("%d",&i);
if(!i)
{
scanf("%d%d",&i,&j);
printf("%d\n",query(1,1,n,i,j));
}
else
{
scanf("%d%d%d",&i,&j,&k);
update(1,1,n,i,j,k);
}
}
}
}
先上代码,最难理解的是m[o].st=m[L].st | m[R].st相当于对L,R进行归并将他们的所有含有1的位数全部归并到m[o].st上,因此如果x & m[o].st没有变化,也就是说,对区间所有元素都不需要修改,则返回,这一点理解了代码看起来也就比较轻松了
推荐阅读