Enigmatic Partition
3
5 7
7 9
1 9
Case #1: 2
Case #2: 7
Case #3: 8
定义一个函数 f ( n ) f(n) f(n)表示有多少满足以下条件的序列:
现对于给出的每对 l , r l,r l,r,要求 ∑ i = l r f ( i ) \mathop{\sum}\limits_{i=l}^{r}f(i) i=l∑rf(i)。
看到数据范围就想这是道打表的题……
首先观察 f ( n ) f(n) f(n)的条件。它是首尾差等于2并且相邻两个差最多为1的递增序列。
所以,对于每个数列,我们都可以分成3段。现在我们设第一段的数值为 a a a,那么第二段是 a + 1 a+1 a+1,第三段是 a + 2 a+2 a+2,并且设每段的长度为 b 1 , b 2 , b 3 b_1,b_2,b_3 b1,b2,b3。显然, b 1 , b 2 , b 3 ≥ 1 b_1,b_2,b_3\ge 1 b1,b2,b3≥1且 b 1 + b 2 + b 3 = m b_1+b_2+b_3=m b1+b2+b3=m。如图
那么,根据题目,我们可以得出式子:
a b 1 + ( a + 1 ) b 2 + ( a + 2 ) b 3 = n ab_1+(a+1)b_2+(a+2)b_3=n ab1+(a+1)b2+(a+2)b3=n
a ( b 1 + b 2 + b 3 ) + b 2 + 2 b 3 = n a(b_1+b_2+b_3)+b_2+2b_3=n a(b1+b2+b3)+b2+2b3=n
a m + b 2 + 2 b 3 = n am+b_2+2b_3=n am+b2+2b3=n
所以我们可以发现,对于一个 n n n有 4 4 4个未知数 a , m , b 2 , b 3 a,m,b_2,b_3 a,m,b2,b3。其中, b 2 , b 3 b_2,b_3 b2,b3至少为 1 1 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。
接下来看差分的两个变式:
然后我们看刚刚推的式子,发现 a m am am都是比较大的,难以考虑。因此不妨我们确定一个 a , m a,m a,m,然后枚举 b 2 , b 3 b_2,b_3 b2,b3,并列表分析。
确定 a = 1 , m = 6 a=1,m=6 a=1,m=6,前几行往后 b 2 b_2 b2递增。
n | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
---|---|---|---|---|---|---|---|---|---|---|
b 3 b_3 b3=4 | 123333 | |||||||||
b 3 b_3 b3=3 | 112333 | 122333 | ||||||||
b 3 b_3 b3=2 | 111233 | 112233 | 122233 | |||||||
b 3 b_3 b3=1 | 111123 | 111223 | 112223 | 122223 | ||||||
f ( n ) f(n) f(n) | 1 | 1 | 2 | 2 | 2 | 1 | 1 | 0 | 0 | 0 |
差分 | 1 | 0 | 1 | 0 | 0 | -1 | 0 | -1 | 0 | 0 |
隔项 | 1 | 0 | 0 | 0 | -1 | -1 | 0 | 0 | 0 | 1 |
位置 | am+3 | (a+1)m+1 | (a+1)m+2 | (a+2)m | ||||||
美化 | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- |
所以我们在程序里只要枚举 a , m a,m a,m,然后对于每个 a , m a,m a,m,我们只要在上述位置进行加减,然后把差分后的数组搞回原来就可以了。
注意枚举时只要枚举 a a a为 m m m的倍数,然后再带入,避免除法,一开始就是没有这样WA了一发……
#include
#define ll long long
using namespace std;
const int MAXN=1e5+100;
ll f[MAXN<<2],pre[MAXN<<1];
int main()
{
for(int m=3;m<MAXN;m++)
for(int a=m;a<MAXN;a+=m)
f[a+3]++,f[a+m+1]--,f[a+m+2]--,f[a+2*m]++;//在上述位置直接标记
for(int i=3;i<MAXN;i++)
f[i]+=f[i-2];//把隔位的差分搞回来
for(int i=2;i<MAXN;i++)
f[i]+=f[i-1];//再拆一层套娃
pre[1]=f[1];
for(int i=2;i<MAXN;i++)
pre[i]=pre[i-1]+f[i];//前缀和快速求和
int t,l,r;
scanf("%d",&t);
for(int _=1;_<=t;_++){
scanf("%d%d",&l,&r);
printf("Case #%d: %lld\n",_,pre[r]-pre[l-1]);
}//直接输出答案
}
学到了差分太好用了~