hdu 5726 区间gcd RMQ+二分 || 暴力枚举

来源:互联网 发布:傲虎网络 编辑:程序博客网 时间:2024/05/07 18:35


题意:

给定一个序列,每次询问一个区间
输出这个区间上所有数的GCD,以及GCD与其相同的区间个数

思路:

首先我们要考虑到gcd随着区间长度的增加是非递增的,并且任何一个区间gcd的个数是log级别的 。所以对于这个题目我们可以固定右端点,然后枚举左端点,统计不同的gcd的个数,同时用map记录gcd的数量即可.
用一个vector 对应两个键值一个是区间的gcd,一个是从第i个位置往前所能扩张到的最远下标。由gcd性质可知,vector存的gcd是非递增的.而且当第i个数作为右端点时,第i-1个数已经做过了,那么只需要利用第i-1个的把第i的数a[i]加入即可。
复杂度 nlogn

#include<bits/stdc++.h>#define pb push_back#define mk make_pair#define P pair<int,int> using namespace std;typedef long long ll;const int maxn = 1e5 + 5;int n,q;int a[maxn];vector<P >vt[maxn];map<int,ll>mp;int main(){    int _;    cin>>_;    int ca = 1;    while(_--)    {        int len;        mp.clear();        scanf("%d",&n);        for(int i = 1;i <= n; ++i)        scanf("%d",a + i),vt[i].clear();        for(int i = 1;i <= n;++i)        {            mp[a[i]]++;            vt[i].pb(mk(a[i],i));            len = 0;            for(int j = 0;j < vt[i-1].size() ;++j)            {                int tmp = __gcd(a[i],vt[i-1][j].first);                mp[tmp] += vt[i][len].second - vt[i-1][j].second;                if(tmp == vt[i][len].first)//由递减性质只可能和前一个相同.                 vt[i][len].second = vt[i-1][j].second;                else                vt[i].pb(mk(tmp,vt[i-1][j].second)),len++;            }        }        scanf("%d",&q);        printf("Case #%d:\n",ca++);        while(q--)        {            int l,r;            int ans;            scanf("%d %d",&l,&r);            if(l >= vt[r][0].second)            ans = vt[r][0].first;            else            {                for(int i = 1;i < vt[r].size(); ++i)                {                    if(vt[r][i-1].second > l && l >= vt[r][i].second)                    {                        ans = vt[r][i].first;                        break;                    }                }            }            printf("%d %lld\n",ans,mp[ans]);        }       }    return 0; } 

当然,一般处理区间gcd的问题我们可以想到RMQ.和普通的处理区间最值一样,这里的RMQ只是增加了一个gcd而已并没有什么影响.
我们还是根据区间gcd的性质,gcd的个数最多为log级别的,那么我们可以预先处理好所有可能的gcd,然后直接输出即可.
这个过程我们先枚举每个点gcd作为左端点,然后去二分右端点(因为gcd的性质使得gcd具有单调性),那么我们最多会有log个不同的gcd.总体的复杂度为
O(nlognlog)

#include<bits/stdc++.h>using namespace std;typedef long long ll;const int mod=1e9+7;const int maxn=1e5+10;int n,q;int dp[maxn][20],lg2[maxn];map<int,ll>mp;void ST(){    for(int i = 2;i <= n;++i)    lg2[i] = lg2[i/2]+1;    for(int j = 1;(1 << j) <= n;++j)    {        for(int i = 1;i + (1 << j) - 1 <= n;++i)        {            dp[i][j] = __gcd(dp[i][j-1] ,dp[(1<<(j-1))+i][j-1]);        }    }    return ;}int query(int l,int r){    int k = lg2[r-l+1];    return __gcd(dp[l][k],dp[r-(1<<k)+1][k]);}void solve(){    mp.clear();    for(int i = 1;i <= n;++i)    {        int g = dp[i][0],j = i;        while(j <= n)        {            int l = j,r = n;            while(l <= r)            {                int mid = (l + r)>> 1;                if(query(i,mid) == g) l = mid+1;                else r= mid - 1;            }            mp[g] += (r-j+1);            j = r + 1;            g = query(i,j);        }    }}int main(){    int _;    cin>>_;    int ca = 1;    while(_--)    {        scanf("%d",&n);        for(int i = 1;i <= n;++i)        scanf("%d",&dp[i][0]);        ST();        solve();        scanf("%d",&q);        printf("Case #%d:\n",ca++);        while(q--)        {            int l,r;            scanf("%d %d",&l,&r);            int gcd = query(l,r);            printf("%d %lld\n",gcd,mp[gcd]);        }     }    return 0;}
原创粉丝点击