题解、Writeup、游记和碎碎念
YOUSIKI 学习了递推,于是他请皮皮妖给他出道题,皮皮妖说:
f(1)=1,f(i)=i-f(i-1),求 f(n)
YOUSIKI 看了一眼把它秒切了,于是他要求皮皮妖加大难度,皮皮妖想了想,说:
f(1)=1,f(i)=i-f(f(i-1)),求 f(n)
YOUSIKI 看了两眼把它秒切了,于是他要求皮皮妖加大难度,皮皮妖想了想,说:
f(1)=1,f(i)=i-f(f(f(i-1))),求 f(n)
YOUSIKI 看了三眼把它秒切了,于是他要求皮皮妖加大难度,皮皮妖想了想,说:
...
...
...
YOUSIKI 看了 m 眼,但是没有能秒切,于是他找到你,请你帮他解决这个问题。
一行两个正整数 n,m。n<=10^18,m<=10^6
一行一个整数 f(n)
4 2
3
样例解释:f(1)=1,f(2)=2-f(f(1))=1,f(3)=3-f(f(2))=2,f(4)=4-f(f(3))=3
为了描述简洁,把 记为 。
显然,,设 表示最大的 ,使得对于任意 ,。
可以发现 的转移可以表示为:
,对于 ,若 ,那么 ,否则 (正确性显然)。
如果有办法求出 ,那么 。
假设 ,将 的表列出:
0 0 1 2 3 4 0 5 0 1 6 0 1 2 7 0 1 2 3 8
换一种写法:
0
0
1
2
3
4 0
5 0 1
6 0 1 2
7 0 1 2 3
8 0 1 2 3 4 0
9 0 1 2 3 5 0 4 0 1
可以发现从第二行开始(首行记为第 行),每一行是把上一行的数复制一遍,加 ,然后在每个 的数后面加上 得到的(正确性显然)。
如果把 的数都写成 ,那么表会变成这样:
0
0
1
2
3
4 0
4 0 1
4 0 1 2
4 0 1 2 3
4 0 1 2 3 4 0
4 0 1 2 3 4 0 4 0 1
可以发现对于 ,第 行是第 行的前缀,且第 行多出的部分是第 行的内容(可使用数学归纳法证明)。
回到原问题,现在需要求 。
到 的 一定包含若干整行和最后不满一行的部分。
对于每一行,可以用 表示这一行数的总数, 表示这一行 \le m-1 的数的个数。
显然,,当 时,。
事实上 并没有必要计算,因为 。
这样已经可以解决前面的整行了。
对于后面不满一行的部分,可以从最后一行开始往前枚举,如果当前行的大小不超过需要的大小,那么就可以把当前行加入答案(在当前需要大小为 时,需要特判只有一个 的情况)。
这一部分可以由前面关于前缀的结论证明。
然后这题就完了。
其他题解都直接给个莫名其妙的结论就跑,颓了几天这个题,真 爽。
#include<bits/stdc++.h>
typedef long long ll;
const int N=5000007;
ll n,s,ans,a[N];
int m,i,j;
int main()
{
scanf("%lld%d",&n,&m);
if(n==1)return puts("1"),0;
if(n<=m+2)return printf("%lld",n-1),0;
for(i=0;i<=m;i++)a[i]=1;
a[m+1]=2;
s=m+4;
for(i=m+2;s<=n;i++)
s+=a[i]=a[i-1]+a[i-m];
i--;
for(j=0;j+1<i;j++)ans+=a[j];
n-=s-a[i];
for(;n>1;i--)
if(n>=a[i])n-=a[i],ans+=a[i-1];
printf("%lld",ans+1);
}
日期: 2017-06-09
这是一篇旧文,原始文章及评论可在 https://oldblog.mcfx.us/archives/238/ 查看。