原题请看这里
数字 n n n的分区是所有数字之和等于 n n n的集合。
如果分区 n = a 1 + a 2 + . . . + a m n = a_1 + a_2 + ... + a_m n=a1+a2+...+am满足以下签名,则称为神秘分区:
令 f ( n ) f(n) f(n)为 n n n的神秘分区的数量。 给定 l l l和 r r r,请计算 ∑ i = l r f ( i ) \sum_ {i = l} ^ r f(i) ∑i=lrf(i)
第一行包含测试用例的数量 T ( 1 ≤ T ≤ 10 , 000 ) T(1 \le T \le 10,000) T(1≤T≤10,000)。
在下面的每条 T T T行中,都有两个整数 l l l和 r ( 1 ≤ l ≤ r ≤ 100 , 000 ) r(1 \le l \le r \le 100,000) r(1≤l≤r≤100,000)。
对于每个测试用例,输出一行包含 C a s e Case Case # x x x: y y y 的行,其中x是测试用例编号,y是答案。
3
5 7
7 9
1 9
Case #1: 2
Case #2: 7
Case #3: 8
f(1) = 0.
f(2) = 0.
f(3) = 0.
f(4) = 0.
f(5) = 0.
f(6) = 1: 6 = 1 + 2 + 3.
f(7) = 1: 7 = 1 + 1 + 2 + 3.
f(8) = 2: 8 = 1 + 1+ 1 + 2 + 3, 8 = 1 + 2 + 2 + 3.
f(9) = 4: 9 = 1 + 1 + 1 + 1 + 2 + 3, 9 = 1 + 1 + 2 + 2 + 3, 9 = 1 + 2 + 3 + 3, 9 = 2 + 3 + 4
考虑差分。
首先,我们观察题目所给的 f ( n ) f(n) f(n)的条件:首项末项相差为二,且相邻数字的差值最多为 1 1 1的不下降序列。
所以对于每一个这样的序列我们都可以分成三段:
根据题目意思,我们可以列出下面的等式:
这里我们可以发现,对于每一个 n n n都可以分成 4 4 4个未知数: v a l , m , a 2 , a 3 val,m,a_2,a_3 val,m,a2,a3,其中 a 2 , a 3 ≥ 1 a_2,a_3 \ge 1 a2,a3≥1
想到这里,我们就要引入解这题的重要方法————差分
差分用来求解区间加减求和的问题。
实现:对于每一个区间,我们在差分数组上对这个区间的第一个位置加上修改值,最后一个位置减去修改值,再对差分数组求前缀和,那么这个得到的前缀和数组就是最后经过修改的数组。
简单来说,设原数组是 a [ ] a[] a[],差分数组是 b [ ] b[] b[],那么 a i = b i − b i − 1 a_i=b_i-b_{i-1} ai=bi−bi−1
对于差分,还有两个变式:
二阶差分其实就是差分里再套一个差分,假设要减去或加上一段有规律的数字,可以对差分数组再进行差分,得到一个二阶差分数组,就可以把有规律的转化成同样的数值。
隔项差分就是隔一项差分一次。
介绍完差分,回到本题:
刚刚我们推出了:
v a l m + a 2 + 2 a 3 = n valm+a_2+2a_3=n valm+a2+2a3=n
所以我们只要枚举 v a l val val和 m m m的值,在区间首尾进行标记,最后变回原来的数组就可以了
#include
#define ll long long
const int MAXN=1e5+10;
ll t,l,r,qian[MAXN<<1],f[MAXN<<2];
int main(){
for(int i=3;i<MAXN;++i)
for(int j=i;j<MAXN;j+=i){
f[j+3]++;
f[j+2*i]++;
f[j+i+1]--;
f[j+i+2]--;
}
for(int i=3;i<MAXN;++i) f[i]+=f[i-2];
for(int i=1;i<MAXN;++i){
f[i]+=f[i-1];
qian[i]=qian[i-1]+f[i];
}
scanf("%lld",&t);
for(int Case=1;Case<=t;Case++){
scanf("%lld%lld",&l,&r);
printf("Case #%d: %lld\n",Case,qian[r]-qian[l-1]);
}
}