• 一个关于序列区间gcd的小trick


    trick的来源

    有一道题目是这样的:

    对于一个由正整数组成的序列,Magical GCD 是指一个区间的长度乘以该区间内所有数字的最大公约数。给你一个序列,求出这个序列最大的 Magical GCD。
    (nleq 10^5,a_i leq 10^{12})

    暴力的做法是:从左往右枚举右端点(r),再从(r)开始从大到小枚举左端点(l),同时记录([l,r])(gcd),更新答案

    这个暴力显然是(O(n^2))的,考虑优化这个暴力。

    用到有一个极其有用的性质:

    固定一个右端点(r),那么设(g[l]=gcd(a_l,a_{l+1},a_{l+2},...,a_r)),所有(1leq lleq r)(g[l])最多只有(logV)种取值,且相同的(g[l])是连续的,(V)是值域。

    性质的证明是非常简单的,固定右端点(r)后,我们从(r)开始从大到小枚举(l),那么(g[l])要么不变,要么变小,变小的话至少是除以(2),因此经过(logV)次变小就变成了(1),后面就都是(1)了。

    根据这个性质的优化:

    建立一个双向链表,一开始(i)的前驱是(i-1),后继是(i+1)。还是从左往右枚举(r),但是这次从左往右沿着双向链表枚举(l),更新(g[l])(就是g[l]=gcd(g[l],a[r]))。如果(g[l]=g[pre[l]]),那么我们就从双向链表中删掉(l)。这样做就能保证任何时候(g[l])(g[nxt[l]])是不同的,只要沿着双向链表枚举就能找到所有(g[l])相同的那(logV)段。

    结合代码理解:

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    
    const int N = 100007;
    typedef long long ll;
    
    int T, n;
    
    ll a[N], g[N];
    int nx[N], pr[N];
    
    ll gcd(ll x, ll y)
    {
    	return y ? gcd(y, x % y) : x;
    }
    
    void init()
    {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    	for (int i = 1; i <= n; i++) nx[i] = i + 1, pr[i] = i - 1, g[i] = a[i];
    }
    
    void solve()
    {
    	ll ans = 0;
    	for (int r = 1; r <= n; r++)
    		for (int l = 1; l <= r; l = nx[l])
    		{
    			g[l] = gcd(g[l], a[r]);
    			if (g[l] * (r - l + 1) > ans)
    				ans = g[l] * (r - l + 1);
    			if (g[l] == g[pr[l]])
    			{
    				nx[pr[l]] = nx[l];
    				pr[nx[l]] = pr[l];
    			}
    		}
    	printf("%lld
    ", ans);
    }
    
    int main()
    {
    	scanf("%d", &T);
    	while (T--) init(), solve();
    	return 0;
    }
    
  • 相关阅读:
    strict aliasing
    加密数据包加解密部分逆向跟踪
    自定义session扫描器
    同步容器类ConcurrentHashMap及CopyOnWriteArrayList
    CountDownLatch闭锁
    volatile关键字与内存可见性
    原子变量与CAS算法
    C语言笔记一
    小组讨论4
    201920201学期 20192416《网络空间安全专业导论》第六周学习总结
  • 原文地址:https://www.cnblogs.com/zjlcnblogs/p/13155061.html
Copyright © 2020-2023  润新知