codeforces 914 D Bash and a Tough Math Puzzle

Description
Bash likes playing with arrays. He has an array a1 ,  a2 , … an of n integers. He likes to guess the greatest common divisor (gcd) of different segments of the array. Of course, sometimes the guess is not correct. However, Bash will be satisfied if his guess is almost correct.

Suppose he guesses that the gcd of the elements in the range [l, r] of a is x. He considers the guess to be almost correct if he can change at most one element in the segment such that the gcd of the segment is x after making the change. Note that when he guesses, he doesn’t actually change the array — he just wonders if the gcd of the segment can be made x. Apart from this, he also sometimes makes changes to the array itself.

Since he can’t figure it out himself, Bash wants you to tell him which of his guesses are almost correct. Formally, you have to process q queries of one of the following forms:

  • 1 l r x — Bash guesses that the gcd of the range [l, r] is x. Report if this guess is almost correct.
  • 2 i y — Bash sets ai to y.

Note: The array is 1-indexed.
Input

The first line contains an integer n (1 ≤ n ≤ 5·10 5 ) — the size of the array.

The second line contains n integers a1 ,  a2 , …,  an (1 ≤  ai  ≤ 10 9 ) — the elements of the array.

The third line contains an integer q (1 ≤ q ≤ 4·10 5 ) — the number of queries.

The next q lines describe the queries and may have one of the following forms:

  • 1 l r x (1 ≤ l ≤ r ≤ n, 1 ≤ x ≤ 10 9 ).
  • 2 i y (1 ≤ i ≤ n, 1 ≤ y ≤ 10 9 ).

Guaranteed, that there is at least one query of first type.
Output

For each query of first type, output “YES” (without quotes) if Bash’s guess is almost correct and “NO” (without quotes) otherwise.
Examples

InputOutput
3
2 6 3
4
1 1 2 2
1 1 3 3
2 1 9
1 1 3 2
YES
YES
NO
InputOutput
5
1 2 3 4 5
6
1 1 4 2
2 3 6
1 1 4 2
1 1 5 2
2 5 10
1 1 5 2
NO
YES
NO
YES

Note

In the first sample, the array initially is {2, 6, 3}.

For query 1, the first two numbers already have their gcd as 2.

For query 2, we can achieve a gcd of 3 by changing the first element of the array to 3. Note that the changes made during queries of type 1 are temporary and do not get reflected in the array.

After query 3, the array is now {9, 6, 3}.

For query 4, no matter which element you change, you cannot get the gcd of the range to be 2.


题意:n个数的数列,对它有下面两种操作

  • 猜 l ~ r 这部分数的最大公因数为x,如果x不是最大公因数,但只改变l ~ r中的一个数就能让x是最大公因数,那么可以认为是猜中了;
  • 把某一个数 i 的值变为 y。

思路:
更新操作是比较简单的,只需要用log(n)找到这个数所在的叶子,修改它和所有与他相关的父亲即可。
询问操作的处理比较复杂,首先想到的是,为了检查x,需要求所询问区间的gcd,就要搜索所有相关的区间结点,求出gcd后再进一步操作。但是由于这个二叉树维护的是最大公因数,父节点维护的gcd一定可以整除子节点维护的gcd,可以利用这个性质来求解。

  • 假设要求的范围是[ l , r],猜的gcd是x。由线段树的性质,可以找到[ l , r]由若干个区间结点[ l1 , r1 ], [ l2 , r2 ], … ,[ ln , rn ] 形成的划分。
  • 因为只能修改一个叶子结点来使x正确,而这个叶子节点只由上面的某一个区间结点维护,所以修改过后只能使一个区间的gcd发生变化,所以在检查每个区间结点时,最多只有一个结点的gcd不能被x整除,否则x是错误的
  • 处理这个不能被整除的区间[ li , ri ]
    • 假如x是almost right的,但是我们最多只能改这个区间内的一个叶子节点。由于左右子节点维护的区间是父亲结点的划分 ,那么修改过后只有一个子节点的值会发生改变,所以x至少应该能整除某一个子节点的gcd。如果两个子节点都不能整除,那么x是错误的;否则进入不能整除的子节点重复上述步骤。
  • 当不能被整除的区间是叶子结点时,假设x是almost right的,如果把这个叶子节点修改为x,根据之前的假设,父节点的gcd将变为x,父节点的父节点的gcd也变为x,…。最终,[ l1 , r1 ], [ l2 , r2 ], … ,[ ln , rn ] 这些区间的gcd里,某一个原来不能被x整除的现在变成了x,而其他区间的gcd能被x整除,所以最后总的gcd就是x。
#include <stdio.h>
#include<string.h>



#define maxn 500000
#define fath(i) (i>>1)
#define left(i) (i<<1)
#define right(i) ((i<<1)+1)
#define mid(l,r) (l+ ((r -l) >> 1))

//完全二叉树用数组存放
int l[(maxn << 3) + 5] = { 0 };
int r[(maxn << 3) + 5] = { 0 };
int k[(maxn << 3) + 5] = { 0 };
//int lzy[(maxn << 2) + 5] = { 0 };

int gcd(int a, int b)
{
    while (b != 0)
    {
        int r = b;
        b = a % b;
        a = r;
    }
    return a;
}


int n;
void build(int cl, int cr, int i) {
    l[i] = cl; r[i] = cr;
    k[i] = 1;
    if (cl != cr) {
            build(cl, mid(cl, cr), left(i));
        build(mid(cl, cr) + 1, cr, right(i));
        k[i] = gcd(k[left(i)], k[right(i)]);
    }
    else    scanf("%d", &k[i]); 
}

int update(int num, int vol, int i) {

    if (l[i]==r[i]) {
        k[i] = vol;
        return vol;
    }
    int m = mid(l[i], r[i]);

    if (num <= m)
    {
        return k[i] = gcd(update(num, vol, left(i)), k[right(i)]);
    }
    else if (num >= (m + 1)) {
        return k[i] = gcd(update(num, vol, right(i)), k[left(i)]);
    }
}


int cnt;
bool guess(int cl,int cr,int gs,int i){
    int m = mid(l[i], r[i]);
    if (l[i] == r[i]) { //当前是叶子节点
        if( k[i]%gs==0)return true; //当前叶子不需要替换
        else //当前叶子需要替换
        { 
            cnt++; //记录需要替换的区间的次数
            return cnt <= 1;//如果多于一个那gs就是错的 
        }

    }

    if (cl == l[i] && cr == r[i]) {//[l,r]的划分
        int kl = k[left(i)];
        int kr = k[right(i)];
        if (gs==k[i])return true;//能整除

        //左边可以被整除则检查有没有可能修改右边为gs
        if (kl%gs==0)return guess(m + 1, cr, gs ,right(i));

         //右边可以被整除则检查有没有可能修改左边为gs
        if(kr%gs==0)return guess(cl, m, gs,left(i));
        //都不能被整除
        return false;
    }

    if (cr <= m)//所询问范围全部在左子节点中
    {
        return guess(cl, cr,gs, left(i));
    }
    else if (cl >= (m + 1)) {//所询问范围全部在右子结点中
        return guess(cl, cr,gs, right(i));
    }
    else {//所询问范围左右子节点都维护了一部分
        return (guess(cl, m, gs,left(i))&&guess(m + 1, cr, gs,right(i)));//这些区间的答案不能有一个是false,而且最多替换一个区间
    }
}

    int qry;
    int a1, a2, a3;
int main() {

        scanf("%d", &n);
        build(1, n, 1);
        int q;

        scanf("%d", &q);
        for (int i = 0; i < q; ++i) {
            scanf("%d", &qry);
            if (qry == 1)
            {
                scanf("%d %d %d", &a1, &a2, &a3);
                cnt = 0;
                if (guess(a1, a2, a3,1) == true&&cnt<=1)printf("YES");
                else printf("NO");
                if (i < q - 1)printf("\n");
            }
            else {
                scanf("%d %d", &a1, &a2);
                update(a1, a2, 1);
            }
        }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值