hdu5381 ST+二分+莫队

本文介绍了一种使用莫队算法解决区间GCD求和问题的方法。通过预处理和巧妙的数据结构维护,实现了高效的状态转移。适用于处理大量区间查询问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

//题意:多个区间询问,问区间[l,r] 的所有子区间的gcd和
//思路: 
//1e4 考虑莫队,每次[l,r]转移到[l-1,r]需要加上sum=gcd[l-1,l-1]+gcd[l-1,l]+gcd[l-1,l+1]....+gcd[l-1,r]; 

//由于gcd特性,gcd[l-1,l-1],gcd[l-1,l],gcd[l-1,l+1]....gcd[l-1,r]是递减的,且值的数目不会超过logn  
// 
//莫队要logn求出sum: 所以我们需要维护出lft 跟 rft  维护出累计gcd值,跟转折下标 (lft定义看下面) 

//计算转移: [l,r]转移到[l-1,r]我们只要暴力遍历lft 利用转折下标的差值*该累计gcd 计算sum 

//代码思路:
//A 预处理ST
//B 预处理lft rft
//C 离线莫队,计算转移 
//难度:难
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e4+15;

struct mo{int id,ql,qr;}q[maxn];
int bel[maxn];int col[maxn];ll res[maxn];

struct node{int pos,gcd;node(){}node(int pos,int gcd):pos(pos),gcd(gcd){};};
vector<node>lft[maxn];vector<node>rft[maxn];
//lft定义: 比如lft[2]:固定左端点2 右端点滑动2~n 对应的累计gcd值与转折下标 

//原数组下标: 2 3 4 5 6 7 8 9 ....  13      
//累计gcd:   8 8 8 8 8 4 4 2 2 2 1 1     (gcd(a2,a3),gcd(a2,a3,a4)......)
//转折下标           ↑   ↑     ↑   ↑ 
//lft: lft[2][0]=(6,8),lft[2][1]=(8,4),lft[2][2]=(11,2),lft[2][3]=(13,1) 

//lft内存会不会炸?:固定左端点2 顶多logn个gcd值,所以内存不会炸 
//lft维护方法:与hdu5726维护的方法一样,二分+RMQ求出转移下标跟累计gcd值 
int dp[maxn][19];int logg[maxn];int a[maxn];
void ST(int n)//维护区间gcd RMQ 
{
    for(int i=1;i<=n;i++)dp[i][0]=a[i],logg[i]=log2(i);
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            int x=dp[i][j-1],y=dp[i+(1<<j-1)][j-1];
            dp[i][j]=__gcd(x,y);    
        }    
} 
int RMQ(int l,int r)
{
    int len=logg[r-l+1];
    int x=dp[l][len],y=dp[r-(1<<len)+1][len];
    return __gcd(x,y);
}

int lsearch(int l,int r,int qq,int i)//固定左端点 
{
    int ans=0;
    while(l<=r)
    {
        int m=l+r>>1;
        if(RMQ(i,m)>=qq)l=m+1,ans=m;
        else r=m-1;
    }
    return ans;
}
int rsearch(int l,int r,int qq,int i)//固定右端点 
{
    int ans=0;
    while(l<=r)
    {
        int m=l+r>>1;
        if(RMQ(m,i)<qq)l=m+1 ;//手抖写成RMQ(i,m)一直没发现,debug两个钟 
        else r=m-1,ans=m;
    }    
    return ans;
} 
void init(int n)//维护lft rft 
{
    for(int i=1;i<=n;i++)
    {
        lft[i].clear();
        int la=i;int qq=RMQ(i,i);
        while(1)
        {
            int ans= lsearch(la,n,qq,i);//二分找下一个转折下标ans 
            lft[i].push_back(node(ans,qq));
            if(ans==n)break;
            la=ans+1;
            qq=RMQ(i,la);
        }
		rft[i].clear();
        la=i; qq=RMQ(i,i);
        while(1)
        {
            int ans=rsearch(1,la,qq,i);
            rft[i].push_back(node(ans,qq));
            if(ans==1)break;
            la=ans-1;
            qq=RMQ(la,i);    
        } 
    }
}
bool cmp(mo a,mo b){ if(bel[a.ql]==bel[b.ql])return a.qr<b.qr;return a.ql<b.ql; }
bool cmp2(mo a,mo b){ return a.id<b.id; }
ll ans=0;
void findL(int l,int r,int ty)//莫队转移 
{
    ll tem=0;int la=l;
    for(int i=0;i<lft[l].size();i++)
    {
        ll pos=lft[l][i].pos;ll gcd=lft[l][i].gcd;
        tem+=1ll*(min(1ll*r,pos)-la+1)*gcd;
        la=pos+1;
        if(la>r)break;
    }
    if(ty==1)ans+=tem;
    else ans-=tem;
}
void findR(int l,int r,int ty)//莫队转移 
{
    ll tem=0;int la=r;
    for(int i=0;i<rft[r].size();i++)
    {
        ll pos=rft[r][i].pos;ll gcd=rft[r][i].gcd;
        tem+=1ll*(la-max(1ll*l,pos)+1)*gcd;
        la=pos-1;
        if(la<l)break;
    }
    if(ty==1)ans+=tem;
    else ans-=tem;
}
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        int n;scanf("%d",&n);int unit=(int)sqrt(n);//莫队分块套路 
        for(int i=1;i<=n;i++)scanf("%d",&a[i]),bel[i]=i/unit+1;
        ST(n);init(n);
        //cout<<RMQ(1,5)<<endl;
        //for(int i=0;i<rft[n].size();i++)printf("%d %d\n",rft[n][i].pos,rft[n][i].gcd);
        int qqq;scanf("%d",&qqq);int idx=0;
        while(qqq--)
        {
            idx++;scanf("%d %d",&q[idx].ql,&q[idx].qr);q[idx].id=idx;
        }
        int l=1,r=0;
        sort(q+1,q+1+idx,cmp);ans=0;
        for(int i=1;i<=idx;i++)
        {
         	while(r<q[i].qr)r++,findR(l,r,1);
          	while(r>q[i].qr)findR(l,r,-1),r--;
          	while(l>q[i].ql)l--,findL(l,r,1);
          	while(l<q[i].ql)findL(l,r,-1),l++;
          	//下面的写法WA了 
//          while(l<q[i].ql)findL(l,r,-1),l++;
//          while(l>q[i].ql)findL(l-1,r,1),l--;
//          while(r<q[i].qr)findR(l,r+1,1),r++;
//          while(r>q[i].qr)findR(l,r,-1),r--;
            res[q[i].id]=ans;
        }
        for(int i=1;i<=idx;i++)    printf("%lld\n",res[i]);
    }
}


现在不会TLE了,但还是WA了: #include <bits/stdc++.h> using namespace std; #define int long long const int INF = 0x3f3f3f3f; const int MAX_N = 2e5 + 50; int T, n, m, a[MAX_N]; #define ls(cur) cur << 1 #define rs(cur) cur << 1 | 1 namespace SegmentTree1 { // the case of a[i] <= x long long sum[MAX_N << 2], vtag[MAX_N << 2]; int cnt[MAX_N << 2], stag[MAX_N << 2]; void pushup(int cur) { sum[cur] = sum[ls(cur)] + sum[rs(cur)]; cnt[cur] = cnt[ls(cur)] + cnt[rs(cur)]; } void mark(int cur, int sign, long long val) { sum[cur] *= sign; sum[cur] += cnt[cur] * val; stag[cur] *= sign; vtag[cur] += val; } void pushdown(int cur) { if (stag[cur] != 1) { mark(ls(cur), stag[cur], 0); mark(rs(cur), stag[cur], 0); stag[cur] = 1; } if (vtag[cur]) { mark(ls(cur), 1, vtag[cur]); mark(rs(cur), 1, vtag[cur]); vtag[cur] = 0; } } void build(int cur, int l, int r) { stag[cur] = 1; vtag[cur] = 0; if (l == r) { sum[cur] = cnt[cur] = 0; return ; } int mid = l + r >> 1; build(ls(cur), l, mid); build(rs(cur), mid + 1, r); pushup(cur); } void insert(int cur, int l, int r, int idx, int val) { if (l == r) { sum[cur] = val; cnt[cur] = 1; return ; } pushdown(cur); int mid = l + r >> 1; if (idx <= mid) insert(ls(cur), l, mid, idx, val); else insert(rs(cur), mid + 1, r, idx, val); pushup(cur); } void modify(int cur, int l, int r, int L, int R, int val) { if (L <= l && r <= R) { mark(cur, -1, val); return ; } pushdown(cur); int mid = l + r >> 1; if (L <= mid) modify(ls(cur), l, mid, L, R, val); if (mid + 1 <= R) modify(rs(cur), mid + 1, r, L, R, val); pushup(cur); } long long query(int cur, int l, int r, int L, int R) { if (L <= l && r <= R) return sum[cur]; pushdown(cur); int mid = l + r >> 1; long long res = 0; if (L <= mid) res += query(ls(cur), l, mid, L, R); if (mid + 1 <= R) res += query(rs(cur), mid + 1, r, L, R); return res; } }; namespace SegmentTree2 { // the case of a[i] > x long long sum[MAX_N << 2], tag[MAX_N << 2]; int cnt[MAX_N << 2], mn[MAX_N << 2]; void pushup(int cur) { sum[cur] = sum[ls(cur)] + sum[rs(cur)]; cnt[cur] = cnt[ls(cur)] + cnt[rs(cur)]; mn[cur] = min(mn[ls(cur)], mn[rs(cur)]); } void mark(int cur, long long val) { sum[cur] -= cnt[cur] * val; mn[cur] -= bool(cnt[cur]) * val; tag[cur] += val; } void pushdown(int cur) { if (tag[cur]) { mark(ls(cur), tag[cur]); mark(rs(cur), tag[cur]); tag[cur] = 0; } } void build(int cur, int l, int r, int val[]) { tag[cur] = 0; if (l == r) { sum[cur] = mn[cur] = val[l]; cnt[cur] = 1; return ; } int mid = l + r >> 1; build(ls(cur), l, mid, val); build(rs(cur), mid + 1, r, val); pushup(cur); } void modify(int cur, int l, int r, int L, int R, int val) { if (l == r && mn[cur] <= val) { SegmentTree1::insert(1, 1, n, l, mn[cur]); sum[cur] = cnt[cur] = 0, mn[cur] = INF; return ; } if (L <= l && r <= R && mn[cur] > val) { mark(cur, val); return ; } pushdown(cur); int mid = l + r >> 1; if (L <= mid) modify(ls(cur), l, mid, L, R, val); if (mid + 1 <= R) modify(rs(cur), mid + 1, r, L, R, val); pushup(cur); } long long query(int cur, int l, int r, int L, int R) { if (L <= l && r <= R) return sum[cur]; pushdown(cur); int mid = l + r >> 1; long long res = 0; if (L <= mid) res += query(ls(cur), l, mid, L, R); if (mid + 1 <= R) res += query(rs(cur), mid + 1, r, L, R); return res; } }; signed main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> T; while (T--) { cin >> n >> m; for (int i = 1; i <= n; i++) cin >> a[i]; SegmentTree1::build(1, 1, n); SegmentTree2::build(1, 1, n, a); for (int opt, l, r, x; m--; ) { cin >> opt >> l >> r; if (opt == 1) { cin >> x; SegmentTree2::modify(1, 1, n, l, r, x); SegmentTree1::modify(1, 1, n, l, r, x); } else { long long ans1 = SegmentTree1::query(1, 1, n, l, r); long long ans2 = SegmentTree2::query(1, 1, n, l, r); cout << ans1 + ans2 << '\n'; } } } return 0; }
最新发布
07-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值