欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

最小生成树 找最小值 区间&运算修改

程序员文章站 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

  • 1N, Q105
  • 1Ai, X109
  • 1LRN

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没有变化,也就是说,对区间所有元素都不需要修改,则返回,这一点理解了代码看起来也就比较轻松了