题面:
题目描述:
题意很简单:1.数组中的所有整数都在区间[l, r],2.所有元素之和能被3整除。现在知道这个数组的大小,l和r,问:按照题目的要求组成的数组一共有多少种可能。
题目分析:
这道题应该是一道常见的用dp来解决计数的问题。
1.如果忽略所有元素之和能被3整除,那么,按照正常的想法答案肯定是:(r-l+1)n。但是题目多了所有元素之和能被3整除,看似难度大了很多。其实,我们可以分析一下:忽略所有元素之和能被3整除,答案是怎样想出来的:每一个“位”上都有(r-l+1)种选择,然后把每个“位”上的选择乘起来,就是(r-l+1)n。因此,我们可以通过研究每个"位"上的选择来解决这个问题。但在实际情况中,这个“位”的选择实在是不好解决:要确定所有元素之和要被3整除,有些“位”可能是之前不能被3整除,经过几次累加后就能被3整除了,情况过于复杂。这时,我们要想一种能“简化过程”的思想:
把问题拆解子问题,对应的解决办法其中就有dp。我们可以这样定义问题:前n个数的和能被3模余p(p = 0, 1, 2)的可能有多少?那么其中一个子问题是:前n-1个数的和能被3模余p(p = 0, 1, 2)的可能有多少?换成更直观的图,其实就是:
前n-1位已经确定好(计算出前n-1位的可能数),如果确定了第n位的选择,那么根据组合数学计数原理,就可以用前n-1位计算出的结果计算出前n位一共有多少种选择。第n位的选择要看当前的“状态”是什么:也就是和p有关。
2.经过分析,有如下选择(这里用dp[p][n]来代表上面的含义):
当p == 0时:dp[0][n] += dp[0][n-1] * (第n位选被3模后余数为0的数的个数)dp[0][n] += dp[1][n-1] * (第n位选被3模后余数为2的数的个数)dp[0][n] += dp[2][n-1] * (第n位选被3模后余数为1的数的个数)当p == 1时:dp[0][n] += dp[0][n-1] * (第n位选被3模后余数为1的数的个数)dp[0][n] += dp[1][n-1] * (第n位选被3模后余数为0的数的个数)dp[0][n] += dp[2][n-1] * (第n位选被3模后余数为2的数的个数)当p == 2时:dp[0][n] += dp[0][n-1] * (第n位选被3模后余数为2的数的个数)dp[0][n] += dp[1][n-1] * (第n位选被3模后余数为1的数的个数)dp[0][n] += dp[2][n-1] * (第n位选被3模后余数为0的数的个数)
我们先看看当p == 0时,是怎样得到结果的:有三种情况:第一种情况:前n-1个数的和取模后余数为0且第n个数选余数为0的数,这样才能保证前n位之和取模后余数为0(也就是p);第二种情况:前n-1个数的和取模后余数为1且第n个数选余数为2的数,这样才能保证前n位之和取模后余数为0(也就是p);第三种情况同理可得,p == 1和p == 2也同理可得。
3.现在的问题关键是:第n位选被3模后余数为p的数的个数怎么算:我们可以思考这样一个问题:1-x中,被3取模后余数为p的数的个数 fp(x) 是多少?假如我们解决这个问题,就可以用 fp(r) - fp(l-1) 来算出答案了。因为p的选择不多(p = 0, 1, 2),所以我们直接分类讨论就行了:
1-x中,被3取模后余数为0的数的个数:f0(x) = x / 31-x中,被3取模后余数为1的数的个数:f1(x) = (x + 2) / 31-x中,被3取模后余数为2的数的个数:f2(x) = (x + 1) / 3
4.自己注意初始化问题。
AC代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <cmath> 5 #include <algorithm> 6 using namespace std; 7 const long long mod = 1e9+7; 8 const long long maxn = 2e5+5; 9 long long n, l, r; 10 11 long long dp[5][maxn]; 12 13 const int yu[3][3] = {{0, 2, 1}, {1, 0, 2}, {2, 1, 0}}; 14 15 16 int main(){ 17 cin >> n >> l >> r; 18 int t; 19 20 //注意初始化 21 for(int i = 0; i < 3; i++){ 22 t = (3-i)%3; 23 dp[i][1] = ( (r+t)/3-(l+t-1)/3 ); 24 } 25 26 27 for(int i = 2; i <= n; i++){ 28 for(int k = 0; k < 3; k++){ 29 for(int p = 0; p < 3; p++){ 30 t = (3-yu[k][p]) % 3; 31 dp[k][i] += dp[p][i-1]*( (r+t)/3-(l+t-1)/3 ) % mod; 32 dp[k][i] %= mod; 33 } 34 } 35 } 36 37 cout << dp[0][n] << endl; 38 39 return 0; 40 }