凌乱的地下室
P2100 凌乱的地下室
思路一
注:由于本题数据过大,这里只讲解思路以及 $ 50 $ 分代码,高精度请自便。
不妨设当有 \(n\) 个方块时,可能的摆放数为 $f(n) $ 。
显然\(f(1)=1,f(2)=2\)。
当计算 \(f(3)\) 时,不妨进行模拟(用\(1,2,3..\)代表方块):
\[这是f(2)的情况 \begin{bmatrix} \notag 1&2\\ 2&1 \end{bmatrix} \\\notag 显然,计算f(3)时应从以上两种状况转移: \begin{bmatrix} \notag 1&2&3\\ 1&3&2\\ 2&1&3 \end{bmatrix} \\ \therefore f(3)=3\\ \]不妨我们再模拟一下\(f(4)\):
\[\notag f(3)=3 ~~ \begin{bmatrix} \notag 1&2&3\\ 1&3&2\\ 2&1&3 \end{bmatrix} \\ 模拟f(4): \begin{bmatrix} \notag 1&2&3&4\\ 1&2&4&3\\ 1&3&2&4\\ 2&1&3&4\\ 2&1&4&3 \end{bmatrix} \\\therefore f(4)=5 \]由此我们可以大概猜测:数列\(f(n)\)是以\(1,2\)为前两项的斐波那契数列。
其实到这里已经可以做题了,但作为一名优秀的数学学子,这种不完全归纳法是不一定正确的,所以我们不妨浅证明一下:
可以看到,当由 \(n-1\) 递推 \(n\) 时,只有最后一个数为 \(n-1\) 的情况才会产生两种情况,而其他都只会有一种。
\[\notag eg: \\ n=3 ~~~ \begin{bmatrix} \notag 1&2&3&(1)\\ 1&3&2&(2)\\ 2&1&3&(3) \end{bmatrix} \\ n=4时,只有(1)(3)行可以产生2个的情况而(2)仅会产生一种情况\\ 而接下来又会有4行的末尾为4,当递推n=5时就会有四行可以产生两种情况。 \]而这个末尾为\(n\)的情况是如何产生的呢?
自然是在\(n-1\)的每一重情况后面都加上一个\(n\)就得到了。
由此可得:
\[\notag n>=2时,f(n)=f(n-1)+f(n-2) \]再用数学归纳法证明即可。
CODE(50tps)
#include
#define ll long long
using namespace std;
const ll maxn = 3123123;
ll ll_maxn=1e18;
inline ll read_int(){
ll a=0,f=0,g=getchar();
while(g<'0'||g>'9'){if(g=='-') f=1;g=getchar();}
while('0'<=g&&g<='9') a=a*10+g-'0',g=getchar();
return f ? -a : a;
}
inline void write(ll s,bool f){
ll top=0,a[40];
if(s<0) s=-s,putchar('-');
while(s) a[++top]=s%10,s/=10;
if(top==0) a[++top]=0;
while(top) putchar(a[top]+'0'),top--;
if(f) putchar('\n');
}
const ll mod=1e8;
const ll maxm = 5;
struct JZ{
ll jz[maxm][maxm];
ll m,n;
inline void clear(){memset(jz,0,sizeof jz),n=0,m=0;}
JZ operator * (JZ a) const{
JZ b;
b.clear();
b.m=m,b.n=a.n;
for(ll i=1;i<=b.m;i++){
for(ll e=1;e<=b.n;e++){
for(ll k=1;k<=n;k++){
b.jz[i][e]+=jz[i][k]*a.jz[k][e];
b.jz[i][e]%=mod;
}
}
}
return b;
}
inline void _write(){
for(ll i=1;i<=m;i++){
for(ll e=1;e<=n;e++){
write(jz[i][e],0),putchar(' ');
}
puts("");
}
}
inline void build(ll M,ll N){
clear();
m=M,n=N;
for(ll i=1;i<=m;i++){
for(ll e=1;e<=n;e++){
jz[i][e]=read_int();
}
}
}
inline void bz(ll N){
clear();
n=m=N;
for(ll i=1;i<=n;i++) jz[i][i]=1;
}
};
inline void read(){
JZ a1,b1;
ll n=read_int()-1;
a1.clear();
a1.m=2,a1.n=1;
a1.jz[1][1]=1,a1.jz[2][1]=2;
b1.bz(2);
JZ a2;
a2.clear();
a2.m=a2.n=2;
a2.jz[1][1]=0,a2.jz[1][2]=1,a2.jz[2][1]=1,a2.jz[2][2]=1;
while(n){
if(n&1) b1=b1*a2;
a2=a2*a2,n>>=1;
}
a1=b1*a1;
write(a1.jz[1][1],1);
}
int main (){
ll T=read_int();
while(T--) read();
}
思路二
既然知道了是斐波那契数列,为什么不直接用其通项公式计算呢?
CODE(100tps)
//题解
#include
using namespace std;
mapmmp; //map优化递归求斐波拉契
long long a,b,c,d;
long long dfs(long long a){
if(a==1) return 1;
if(a==2) return 1;
if(mmp[a]!=0) return mmp[a];
else return
mmp[a]=a%2==0?(dfs(a/2)+2*dfs(a/2-1))*dfs(a/2)%100000000 :
(dfs(a/2)*dfs(a/2)+dfs(a/2+1)*dfs(a/2+1))%100000000;
} //通项公式求斐波拉契
inline long long read(){
long long X=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) X=(X*10+ch-'0')%150000000,ch=getchar();
return X;
} //取摸150000000直接在快速读入中进行
int main(){
a=read();
cout<