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