题目
题意
给出一个最长为
数列
给出一堆最多为
个询问区间,问从这些区间中取出一些数使得数字之和是m的倍数,有多少种方案。其中保证
。
题解
最容易想到的方法就是倍增+dp来做。
定义
表示区间
内,选取数字之和
的方案数。
这种
很容易想到,转移方程也比较容易写,但是空间复杂度会爆炸掉,因此我们必须换一种方法。
离线分治算法:
对于最开始的区间
我们考虑它的中点
,有的询问区间包含了mid这个点,有的在mid点左边,有的在mid点右面。
在这里我们可以用
的时间复杂度内计算出所有包含mid点的询问区间的答案,然后把剩下的询问区间分到左右两边,然后再分治解决。
如何在
的时间复杂度内计算出所有包含mid点的询问区间的答案:
记录
表示区间
之间选取数字之和模m等于k的方案数。
记录
表示区间
之间选取数字之和模m等于k的方案数。
那么
代码
#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;
}