codeforces gym-101741 Subsequence Sum Queries 分治+离线

题目

这里写链接内容

题意

给出一个最长为 200000 数列
给出一堆最多为 200000 个询问区间,问从这些区间中取出一些数使得数字之和是m的倍数,有多少种方案。其中保证 1 m 20

题解

最容易想到的方法就是倍增+dp来做。
定义 f [ i ] [ j ] [ k ] 表示区间 [ l , l + 2 j ) 内,选取数字之和 m o d m == k 的方案数。
这种 d p 很容易想到,转移方程也比较容易写,但是空间复杂度会爆炸掉,因此我们必须换一种方法。

离线分治算法:
对于最开始的区间 [ 1 , n ] 我们考虑它的中点 m i d = ( 1 + n ) / 2 ,有的询问区间包含了mid这个点,有的在mid点左边,有的在mid点右面。
在这里我们可以用 O ( n ) 的时间复杂度内计算出所有包含mid点的询问区间的答案,然后把剩下的询问区间分到左右两边,然后再分治解决。

如何在 O ( n ) 的时间复杂度内计算出所有包含mid点的询问区间的答案:
记录 f [ i ] [ k ] 表示区间 [ l , m i d ] 之间选取数字之和模m等于k的方案数。
记录 g [ i ] [ k ] 表示区间 [ m i d + 1 , i ] 之间选取数字之和模m等于k的方案数。
那么 [ l , r ] ( l m i d r ) 0 m 1 f [ l ] [ i ] g [ r ] [ ( m i ) % m ]

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
#define pr(x) cout<<#x<<":"<<x<<endl
const int maxn = 2e5+7;
int n,m,q;
int query[maxn][3];
int g[maxn][22];
int f[maxn][22];
int a[maxn];
const int mod = 1e9+7;
void solve(int l,int r,vector<int> qs){
    if(l >= r || qs.size() == 0)
        return ;

    int mid = (l + r) / 2;
    for(int i = l;i < r;++i) 
        for(int j = 0;j < m;++j)
            g[i][j] = f[i][j] = 0;

    f[mid][0] = 1;
    for(int i = mid-1;i >= l;i--){
        for(int j = 0;j < m;++j){
            f[i][(j+a[i])%m] = (f[i][(j+a[i])%m] + (long long)f[i+1][j]) % mod;
            f[i][j] = (f[i][j] + f[i+1][j]) % mod;
        }
    }
    g[mid-1][0] = 1;
    for(int i = mid;i < r;++i){
        for(int j = 0;j < m;++j){
            g[i][j] = (g[i][j] + g[i-1][j]) % mod;
            g[i][(j+a[i])%m] = (g[i][(j+a[i])%m] + (long long)g[i-1][j]) % mod;
        }
    }
    vector<int> qs1,qs2;
    for(auto i : qs){

        if(query[i][1] < mid-1) qs1.push_back(i);
        else if(query[i][0] > mid) qs2.push_back(i);
        else {
            if(query[i][1] == mid-1){
                query[i][2] = f[query[i][0]][0];
            }
            else if(query[i][0] == mid){
                query[i][2] = g[query[i][1]][0];
            }
            else{
                for(int j = 0;j < m;++j){
                    query[i][2] = (query[i][2] + 
                            (long long)f[query[i][0]][j] * g[query[i][1]][(m-j)%m])% mod;
                }
            }
        } 
    }
    solve(l,mid,qs1);
    solve(mid,r,qs2); 
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;++i){
        scanf("%d",&a[i]);
        a[i] %= m;
    }
    vector<int> qs;
    scanf("%d",&q);
    for(int i = 0; i < q;++i){
        scanf("%d %d",&query[i][0],&query[i][1]);
        qs.push_back(i);
    }
    solve(1,n+1,qs);
    for(int i = 0;i < q;++i){
        printf("%d\n",query[i][2]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_37517391/article/details/79839775
今日推荐