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

来源:互联网 发布:ubuntu 邮件阅读器 编辑:程序博客网 时间:2024/05/22 02:40

原题见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 10 10(0) 10 20 10(0), 20(1) 10,20 3 10(0), 3(2) 1,3 15 10(0), 3(2), 15(3) 1,3,15 1000 10(0), 15(3), 1000(4) 1,5,1000 60 10(0), 15(3), 1000(4), 60(5) 1,15,20,60 16 10(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 0
原创粉丝点击