HDU 5726 GCD 区间gcd查询 MAP RMQ 优化


原题见HDU 5726

给N( N100000 )个数,Q( Q100000 )个询问,每次查询输出区间的最大公约数,以及最大公约数为这个数的区间数目。

分析

查询次数很多,要做预处理,用map<最大公约数,区间数>存下来,实现O(1)的查询。
预处理发现对于同一左端点的区间而言,右端点越靠右,区间gcd单调递减。因此可以固定左端点,二分右端点,找到gcd突变的右端点,确定对于同一gcd的区间数目。为了查询得更快,用ST表(RMQ)存下区间gcd,只要O(1)即可查询得到区间gcd。

操作

RMQ建表

dp[i][j] 表示长度为 2i 的区间 [i,i+2j1] 范围内的 gcd

dp[i][j]=gcd(dp[i][j1],dp[i+2j][j1])

即区间 [i,i+2j11][i+2j1,i+2j1] 共同确定了区间 [i,i+2j1] gcd

for(int i = 0;i < n;i++){
        dp[i][0] = a[i];
}
for(int j = 1;(1<<j) <= n;j++){
    for(int i = 0;i + (1<<j) - 1 < n;i++){
        dp[i][j] = __gcd(dp[i][j-1], dp[i+(1<<j-1)][j-1]);
    }
}

RMQ询问

[l,r] [l,...][...,r] 共同决定。两者必须共同覆盖了整个区间。区间长度为 2k . k=log2(rl+1)

int ask(int l, int r){
    int k = log(1.0*(r-l+1))/log(2);
    return __gcd(dp[l][k], dp[r-(1<<k)+1][k]);
}

MrBird_to_fly回忆,还有一种对 log2(rl+1) 的优化,就有点邪了,非常省时。

int k=31-__builtin_clz(r-l+1);

int __builtin_ctz (unsigned int x) 返回右起第一个‘1’之后的0的个数。

统计预处理

map<int, int> mp;
int find(int x, int g){
    int l = x, r = n-1;
    if(ask(x, r) > g) return r+1;
    if(ask(x, l) <= g) return l;
    int cnt = 20;
    while(l < r && cnt--){
        int mid =l+r >> 1;
        if(ask(x, mid) > g) l = mid;
        else r = mid;
    }
    if(ask(x, l) > g) return r;
    return l;
}
void makeST(){
    mp.clear();
    for(int i = 0;i < n;i++){
        for(int j = i;j < n;){
            int g = ask(i, j);
            int k = find(i, g-1);
            mp[g] += k-j;
            j = k;
        }
    }
}

优化

上面的预处理左端点逐个枚举,其实重复查询了很多个转折点。预处理复杂度为O(nlog(a)log(a))(a<=10^9)
现在先固定区间右端点,左端点从下标0处开始向右,直到与右端点重合,这过程中gcd逐渐增大,并且也存在转折点。
右端点右移,则可以利用上一个右端点的转折点得到现在的转折点。上一次的转折点要么被舍去(两边的gcd值相等),要么继续保留,但不会再增加(除了右端点作为转折点)。
举例:10 20 3 15 1000 60 16

右端点转折点(下标)gcd
1010(0)10
2010(0), 20(1)10,20
310(0), 3(2)1,3
1510(0), 3(2), 15(3)1,3,15
100010(0), 15(3), 1000(4)1,5,1000
6010(0), 15(3), 1000(4), 60(5)1,15,20,60
1610(0), 60(5), 16(6)1,4,16

每次都以右端点和上一次的转折点对应的gcd得到gcd,只有得到不同gcd才将该转折点保留下来,否则舍去。比如右端点为3时,10,20和3的最大公约数都为1,只需保留10.
需注意的是,右端点和上一次转折点的gcd并不是右边这一栏的gcd。如右端点为15时,转折点为10,为什么对应的gcd为1呢?这是由于1是[10,20,3]的gcd,所以[10,20,3,15]的gcd只能是1的约数。
优化后的复杂度为O(nlog(a))

typedef map <int, LL> MAP;
typedef pair<int, int> PR;
typedef vector<PR> DV;
DV dv, tp;
MAP mp;
void makeTable{
    dv.clear();
    for(int i = 0;i < n;i++){
        int g = -1;
        tp.clear();
        dv.push_back(PR(a[i], i));
        for(DV::iterator it = dv.begin();it != dv.end();it++){
            if(__gcd(it->first, a[i]) != g){
                g = __gcd(it->first, a[i]);
                tp.push_back(PR(g, it->second));
            }
        }
        tp.push_back(PR(0, i+1));
        for(DV::iterator it = tp.begin();it+1 != tp.end();it++)
            mp[it->first] += (it+1)->second-it->second;
        swap(dv, tp);
    }
}

应用

CF 475D. CGCDSSQ
求最大公约数为g的区间数。

数据范围和上面那题差不多,但是时间只有2s,不优化就会T(也许是写得搓)。
预处理优化 142ms
log优化 436ms

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值