• AcWing 246. 区间最大公约数


    传送门

    由于在此题解中加入了许多自己对细节的处理,所以长文警告(雾)

    Description

    给定一个长度为 \(N\) 的数列 \(A\),以及 \(M\) 条指令,每条指令可能是以下两种之一:

    C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 \(d\)
    Q l r,表示询问 A[l],A[l+1],…,A[r] 的最大公约数 (GCD)。
    对于每个询问,输出一个整数表示答案。

    Constraints

    \(N≤500000,M≤100000,1≤A[i]≤10^{18},|d|≤10^{18}\)

    Solution

    Part 1 主要思路

    我们首先的思路是考虑能不能直接去维护两种操作,很可惜这题不行。

    由于这题在求 gcd 同时还会区间加,而可能 \(gcd(a, b)≠gcd(a+x, b+x)\),不满足子区间可合并性,所以不能直接去维护。

    除了辗转相除法求 gcd 以外,我们还能再想到效率稍低的更相减损术(证就不证了)

    \(gcd(a, b)=gcd(a, b-a)\)

    此式子可以推广到多个数,由此可推广到本题,设查询这个区间的左右端点分别为 \(l\)\(r\),则有:

    \(\qquad \qquad \qquad \qquad gcd(a[l], a[l+1], a[l+2],..., a[r]) = gcd(a[l], a[l+1] - a[l], a[l + 2] - a[l+1],...,a[r]-a[r-1])\)

    因此,我们所查询的左式可以转化为右式。

    观察右式,可以发现除第一项 \(a[l]\) 之外,后面的就是原数组 \(a\) 的差分数组!

    \(a\) 的差分数组为 \(b\),则

    \(\qquad \qquad \qquad \qquad gcd(a[l], a[l+1] - a[l], a[l + 2] - a[l+1],...,a[r]-a[r-1])=gcd(a[l],b[l+1],b[l+2],...,b[r])\)

    而去维护差分数组需要维护单点修改与区间 gcd,可以使用线段树或树状数组维护。

    同时,每次查询的第一项 \(a[l]\) 是原数组,需要再建立一棵线段树,去维护原数组的区间修改与单点查询。

    总结:维护两棵线段树,一棵是原数列,区间修改+单点查询(这么做会TLE,仍需经过处理,在Part2中会讲到),一棵是原序列的差分序列,单点修改+区间查询。

    Part 2 一些代码实现的细节

    这题我调了6h+,WA了十几发才过,发现了里面一些需要注意的东西(虽然可能大家都知道)

    如果您是口胡人当我没说

    + 差分数组中 gcd 会出现负数与 \(0\) 情况

    由于 gcd 不能出现负数,又根据 \(gcd(a,b)=gcd(a,-b)\) 可知,直接在手写 gcd 函数注意取绝对值即可。

    但是不能直接为差分数组取绝对值!差分数组能反映原数组相邻数之间的增减情况的,不能更改!

    同时,差分数组也会有 \(0\),而在 gcd 中 \(mod\)\(0\) 就爆炸了,根据 \(gcd(a,0)=a\) ,对 \(0\) 情况特殊处理一下。

    + 差分数组越界情况

    由于差分数组处理是下方这样:

    a[l] += x;  a[r + 1] -= x;
    

    所以可能导致\(r+1\)下表超出线段树 \(n\) 的范围导致蜜汁 \(MLE\),而 \(r+1\) 位置数值的变化我们并不关心,若 \(r+1 \ge n\) 直接特判不进行操作即可。

    在最后输出答案时也要小心,因为也有可能越界!

    + 维护原数组中 \(pushdown\) 下传操作爆 \(long\) \(long\) 的问题

    区间最大长度高达 \(10^6\),在下传中乘一下可能 \(10^{18}\) 大小的 \(d\),开 \(long\) \(long\) 也救不回来 /youl/youl/youl

    高精度的话复杂度不够,只能想办法避免 \(pushdown\) 操作。

    在学习线段数组时,也使用了差分来将区间修改变成了单点修改,在此题中也可以应用。

    将维护原数组的线段树也改变成维护差分数组,只不过这次维护的是区间和。

    式子又可以变成

    \(\qquad \qquad \qquad \qquad gcd(a[l],b[l+1],b[l+2],...,b[r]) = gcd((b[1]+b[2]+...+b[l]),b[l+1],b[l+2],...,b[r])\)

    这次都可以正确地维护了。

    Code

    这是原来的两棵线段树修改而成的,为直观理解,未对两棵线段树的相同操作与函数进行合并。(所以会有点长)

    读者在自行写代码时可以把一些函数进行合并,节省码量。

    // by youyou2007 in 2022.
    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    #include <queue>
    #include <stack>
    #include <map>
    #define int long long
    #define REP(i, x, y) for(int i = x; i < y; i++)
    #define rep(i, x, y) for(int i = x; i <= y; i++)
    #define PER(i, x, y) for(int i = x; i > y; i--)
    #define per(i, x, y) for(int i = x; i >= y; i--)
    #define lc (k << 1)
    #define rc (k << 1 | 1)
    using namespace std;
    const int N = 500100;
    int n, m;
    int a[N], b[N], t[N];
    int f1[N * 4], f2[N * 4];//f1 数组表示差分求和,f2 数组表示差分gcd
    int gcd(int xx, int yy)
    {   
            xx = abs(xx);//细节:gcd里不要出现负数
            yy = abs(yy);
    	if(yy == 0)//细节:gcd 中出现 0 时特判
    	{
    		return xx;
    	}
    	if(xx % yy == 0)
    	{
    		return yy;
    	}
    	else
    	{
    		return gcd(yy, xx % yy);
    	}
    }
    void pushup1(int k)//所有1、2函数除不一样的几行外可以合并成一个函数
    {
    	f1[k] = f1[lc] + f1[rc];
    }
    void pushup2(int k)
    {
    	f2[k] = gcd(f2[lc], f2[rc]);
    }
    void build1(int l, int r, int k)//以下均同理
    {
    	if(l == r)
    	{
    		f1[k] = a[l];
    		return;
    	}
    	int mid = (l + r) / 2;
    	build1(l, mid, lc);
    	build1(mid + 1, r, rc);
    	pushup1(k);
    }
    void build2(int l, int r, int k)
    {
    	if(l == r)
    	{
    		f2[k] = b[l];
    		return;
    	}
    	int mid = (l + r) / 2;
    	build2(l, mid, lc);
    	build2(mid + 1, r, rc);
    	pushup2(k);
    }
    void modify1(int l, int r, int q, int k, int d)//单点修改(求和)
    {
    	if(l == r && l == q)
    	{
    	    f1[k] += d;
    		return;
    	}
    	int mid = (l + r) / 2;
    	if(q <= mid)
    	{
    		modify1(l, mid, q, lc, d);
    	}
    	else
    	{
    		modify1(mid + 1, r, q, rc, d);
    	}
    	pushup1(k);
    }
    void modify2(int l, int r, int q, int k, int d)//单点修改(gcd)
    {
    	if(l == q && r == q)
    	{
    		f2[k] += d;
    		return;
    	}
    	int mid = (l + r) / 2;
    	if(q <= mid)
    	{
    		modify2(l, mid, q, lc, d);
    	}
    	else
    	{
    		modify2(mid + 1, r, q, rc, d);
    	}
    	pushup2(k);
    }
    int query1(int l, int r, int ql, int qr, int k)//区间求和
    {
    	if(l >= ql && r <= qr)
    	{
    		return f1[k];
    	}
    	int mid = (l + r) / 2;
    	if(qr <= mid)
    	{
    		return query1(l, mid, ql, qr, lc); 
    	}
    	else if(ql >= mid + 1)
    	{
    		return query1(mid + 1, r, ql, qr, rc);
    	}
    	else
    	{
    	    return query1(l, mid , ql, mid, lc) + query1(mid + 1, r, mid + 1, qr, rc);
    	}
    }
    int query2(int l, int r, int ql, int qr, int k)//区间gcd
    {
    	if(l >= ql && r <= qr)
    	{
    		return f2[k];
    	}
    	int mid = (l + r) / 2;
    	if(qr <= mid)
    	{
    		return query2(l, mid, ql, qr, lc);
    	}
    	else if(ql >= mid + 1)
    	{
    		return query2(mid + 1, r, ql, qr, rc);
    	}
    	else
    	{
    		return gcd(query2(l, mid, ql, mid, lc), query2(mid + 1, r, mid + 1, qr, rc));
    	}
    }
    signed main()
    {
    	scanf("%lld%lld", &n, &m);
    	rep(i, 1, n)
    	{
    		scanf("%lld", &t[i]);
    		a[i] = t[i] - t[i - 1];
    		b[i] = t[i] - t[i - 1];
    	}
    	build1(1, n, 1);
    	build2(1, n, 1);
    	while(m--)
    	{
    		char opt;
    		cin>>opt;
    		int x, y, z;
    		if(opt == 'C')
    		{
    			scanf("%lld%lld%lld", &x, &y, &z);
    			modify1(1, n, x, 1, z);
    			modify2(1, n, x, 1, z);
    			if(y + 1 <= n)//特判差分数组越界
    		        {
    		    	    modify1(1, n, y + 1, 1, -z);
    			    modify2(1, n, y + 1, 1, -z);
    		        }
    		}
    		else
    		{
    			scanf("%lld%lld", &x, &y);
    			if(x == y && x == n)//输出答案前也应判断越界问题,若只是求最后一个数的gcd便是它本身
    			{
    			    printf("%d\n", query1(1, n, 1, n, 1));
    			    continue;
    			}
    			int temp = gcd(query1(1, n, 1, x, 1), query2(1, n, x + 1, y, 1));
    			printf("%lld\n", temp);  
    		}
    	}
    	return 0; 
    }
      
    
  • 相关阅读:
    inlineblock详解
    jvisualvm JavaVisualVM 远程连接
    C#解决XML反序列化空格值被忽略的问题
    modbus通讯协议
    C语言经典易错题目
    Redis集群报错Node Is Not Empty,Either The Node Already Knows Other Nodes
    GDB的GEF插件
    LLVM基础学习:使用VSCode+GDB 调试 outoftree 的 LLVM Pass 的配置
    GDB 的WEB前端GDBgui
    安全分析工具dr checker的安装
  • 原文地址:https://www.cnblogs.com/pjxpjx/p/16414770.html
Copyright © 2020-2023  润新知