hdu 5726 (线段树 GCD RMQ)多校第一场1004

题意:T组测试数据,每组数据给你n个数,m次询问每次询问包含两个数字l,r。每次查询[l,r]区间内的数的最大公约数。并输出整个序列中最大公约数与[l,r]最大公约数相等的组数。

解题方法:可以通过线段树来记录每段区间内的最大公约数,但是后来统计与[l,r]最大公约数相等的个数时,老是超时。最后也还是在网上查的代码。

但是后来看了也有用RMQ来解题的,后来想了想,RMQ比线段树查询起来可能效率更快。


附线段树ac代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <map>

using namespace std;

#define ll long long

const int maxn=1000010;

struct node{///线段树的存储结构
    int l,r;
    ll gcd;
}Tree[maxn<<2];

ll a[maxn];

ll gcd(ll a,ll b)
{
    return b==0?a:gcd(b,a%b);
}

void pushup(int rt)
{
    Tree[rt].gcd=gcd(Tree[rt*2].gcd,Tree[rt*2+1].gcd);
}

///建立线段树
void Build(int l,int r,int rt)
{
    Tree[rt].l=l,Tree[rt].r=r;
    if(l==r)
    {
        Tree[rt].gcd=a[l];
        return ;
    }
    int mid=(l+r)/2;
    Build(l,mid,rt*2);
    Build(mid+1,r,rt*2+1);
    pushup(rt);
}

///询问线段树
ll queryans(int L,int R,int rt)
{
    if(L<=Tree[rt].l&&Tree[rt].r<=R)
    {
        return Tree[rt].gcd;
    }
    int mid=(Tree[rt].l+Tree[rt].r)/2;
    ll ans=0;
    if(L<=mid) ans=gcd(ans,queryans(L,R,rt*2));
    if(mid<R)  ans=gcd(ans,queryans(L,R,rt*2+1));
    return ans;
}
int n;
map<ll,ll>ans;
map<ll,ll>mp1;//mp1代表以x[i]结尾的所有区间的gcd的个数
map<ll,ll>mp2;//临时变量

int main()
{
    int T;
    scanf("%d",&T);
    int cas=1;
    while(T--)
    {
        scanf("%d",&n);
        ans.clear();
        mp1.clear();
        mp2.clear();
        ///输入数据
        for(int i=1; i<=n; i++) scanf("%I64d",&a[i]);

        Build(1,n,1);///建立线段树
        mp1[a[1]]++;
        ans[a[1]]++;
        ///将查询的ged的结果存到ans中
        for(int i=2; i<=n; i++)
        {
            ll now=a[i];
            mp2[now]++;
            ans[now]++;
            map<ll,ll>::iterator  it;
            for(it=mp1.begin(); it!=mp1.end(); it++)
            {
                int nex=gcd(now,it->first);
                ans[nex]+=it->second;
                mp2[nex]+=it->second;
            }
            mp1.clear();
            for(it=mp2.begin(); it!=mp2.end(); it++)
            {
                mp1[it->first]=it->second;
            }
            mp2.clear();
        }
        int q;
        printf("Case #%d:\n",cas++);
        scanf("%d",&q);
        ///查询输出过程
        while(q--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            ll temp=queryans(l,r,1);
            printf("%I64d %I64d\n",temp,ans[temp]);
        }
    }
}


另外转载一下别的大神的RMQ代码:


RMQ统计预处理[l,r]内的gcd值

枚举左端点,随着右端点增大,gcd值越来越小,二分这些变小的节点,统计出线段的gcd值所作的贡献,map累加每个贡献的数量

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<string>  
#include<vector>  
#include<ctime>  
#include<queue>  
#include<set>  
#include<map>  
#include<stack>  
#include<iomanip>  
#include<cmath>  
#define mst(ss,b) memset((ss),(b),sizeof(ss))  
#define maxn 0x3f3f3f3f  
#define MAX 1000100  
///#pragma comment(linker, "/STACK:102400000,102400000")  
typedef long long ll;  
typedef unsigned long long ull;  
#define INF (1ll<<60)-1  
using namespace std;  
ll a[100100];  
ll mp[100100][65];  
ll gcd(ll a,ll b){  
    return b==0?a:gcd(b,a%b);  
}  
int n,q;  
void RMQ_init(){  
    for(int i=1;i<=n;i++) mp[i][0]=a[i];  
    int m=log(n)/log(2);  
    for(int i=1;i<=m;i++){  
        for(int j=n;j>=1;j--){  
            mp[j][i]=mp[j][i-1];  
            if(j+(1<<(i-1))<=n)  
                mp[j][i]=gcd(mp[j][i],mp[j+(1<<(i-1))][i-1]);  
        }  
    }  
}  
int query(int l,int r){  
    int m=log(r-l+1)/log(2);  
    return gcd(mp[l][m],mp[r-(1<<m)+1][m]);  
}  
map<int,ll>V;  
int main(){  
    int T;  
    scanf("%d",&T);  
    for(int cas=1;cas<=T;cas++){  
        scanf("%d",&n);  
        V.clear();  
        for(int i=1;i<=n;i++) scanf("%I64d",&a[i]);  
        RMQ_init();  
        for(int i=1;i<=n;i++){  
            ll tmp=a[i];  
            int l=i,r=n,mid,ans=l,O=l;  
            while(tmp>1){  
                O=l;r=n;ans=-1;  
                while(l<=r){  
                    mid=(l+r)/2;  
                    if(query(i,mid)<tmp) r=mid-1;  
                    else {  
                        l=mid+1;  
                        ans=mid;  
                    }  
                }  
                if(tmp==1) break;  
                V[tmp]+=ans-O+1;  
                l=ans+1;  
                if(l>n) break;  
                tmp=query(i,l);  
            }  
            if(tmp==1){  
                V[1]+=n-l+1;  
            }  
        }  
        scanf("%d",&q);  
        printf("Case #%d:\n",cas);  
        while(q--){  
            int l,r;  
            scanf("%d%d",&l,&r);  
            ll x=query(l,r);  
            printf("%I64d %I64d\n",x,V[x]);  
        }  
    }  
    return 0;  
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值