2017年浙工大程序设计迎新赛决赛 D 莫队

题目链接


题意:
读入一个长度为 n n n的整数数列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an,以及一个整数 K K K
q q q组询问。
每组询问包含一个二元组 ( l , r ) (l, r) (l,r), 其中 1 ≤ l ≤ r ≤ n 1≤l≤r≤ n 1lrn,
求所有满足以下条件的二元组 ( l 2 , r 2 ) (l_2, r_2) (l2,r2)的数目:
1: 1 ≤ l ≤ l 2 ≤ r 2 ≤ r ≤ n , 1≤l≤l_2≤r_2≤r≤n, 1ll2r2rn,
2: ∑ i = l 2 r 2 a [ i ] \sum_{i=l_2}^{r_2}a[i] i=l2r2a[i] K K K的倍数。


思路:
区间和首先尝试用前缀和进行转化

则对于任意的一个询问区间 [ l , r ] [l,r] [l,r],满足条件的子区间个数为:
A n s = ∑ l 2 = l r ∑ r 2 = l 2 r [ ( s u m [ r 2 ] − s u m [ l 2 − 1 ] ) % k = = 0 ] Ans = \sum_{l_2 = l}^{r} \sum_{r_2 = l_2}^{r} [(sum[r_2] - sum[l_2-1]) \% k == 0] Ans=l2=lrr2=l2r[(sum[r2]sum[l21])%k==0]

若对前缀和数组遍历对 k k k取余,则:
A n s = ∑ l 2 = l r ∑ r 2 = l 2 r [ s u m [ r 2 ] = = s u m [ l 2 − 1 ] ] Ans = \sum_{l_2 = l}^{r} \sum_{r_2 = l_2}^{r} [sum[r_2] == sum[l_2-1]] Ans=l2=lrr2=l2r[sum[r2]==sum[l21]]

故此时询问可以转化为求区间 [ l − 1 , r ] [l-1,r] [l1,r]中取两个相同数的对数。

此时便是经典的莫队问题,可参考BZOJ 2038


代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;

const int A = 1e5 + 10;
ll sum[A],cnt[A],Ans[A],res,k;
int len,n,q;
class Que{
public:
    int l,r,id;
    bool operator<(const Que& rhs)const{
        if(l/len == rhs.l/len) return r/len < rhs.r/len;
        return l/len < rhs.l/len;
    }
}Q[A];
vector<ll> v;

void update(int pos,int v){
    if(v){
        res += cnt[sum[pos]];
        cnt[sum[pos]]++;
    }
    else{
        cnt[sum[pos]]--;
        res -= cnt[sum[pos]];
    }
}

void solve(){
    res = 0;
    memset(cnt,0,sizeof(cnt));

    int L = 0,R = -1;
    for(int i=1 ;i<=q ;i++){
        int id = Q[i].id;
        while(R < Q[i].r) update(++R,1);
        while(L > Q[i].l) update(--L,1);
        while(R > Q[i].r) update(R--,0);
        while(L < Q[i].l) update(L++,0);
        Ans[id] = res;
    }
}


int main(){
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d%d%lld",&n,&q,&k);
        len = sqrt(1.0*n);
        sum[0] = 0;v.push_back(0);
        for(int i=1 ;i<=n ;i++){
            ll x;scanf("%lld",&x);
            sum[i] = (sum[i-1] + x) % k;
            v.push_back(sum[i]);
        }
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());

        for(int i=0 ;i<=n ;i++) sum[i] = lower_bound(v.begin(),v.end(),sum[i]) - v.begin();

        for(int i=1 ;i<=q ;i++){
            scanf("%d%d",&Q[i].l,&Q[i].r);
            Q[i].id = i;Q[i].l--;
        }
        sort(Q+1,Q+1+q);
        solve();
        for(int i=1 ;i<=q ;i++){
            printf("%lld\n",Ans[i]);
        }
        v.clear();
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值