CQOI2018 九连环

文章介绍了如何通过快速傅里叶变换(FFT)优化算法来解决九连环问题中的最小装卸次数计算问题,利用递推关系推导出最终的解法,降低了时间复杂度至O(nlog^2n)。

P4461 [CQOI2018] 九连环

题目大意

n n n连环的装卸要遵守以下两个规则:

  • 一号环(最右边的)在任何时候都可以任意装上或卸下
  • 如果 k k k号环没有被卸下,且 k k k号环右边的所有环都被卸下,则 k + 1 k+1 k+1号环可以任意装上或卸下

求将 n n n连环上的环全部取出的最小装卸次数。

m m m组数据。

1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 10 1\leq n\leq 10^5,1\leq m\leq 10 1n105,1m10


题解

前置知识:快速傅里叶变换(FFT)
f n f_n fn表示将 n n n连环上的环全部取出的最小装卸次数。我们发现,要拆下所有环,首先要将 n n n号环拆下,前提是 1 1 1 n − 2 n-2 n2号环都被拆下,然后将 n n n号环拆下。接下来拆 n − 1 n-1 n1号环,此时 1 1 1 n − 2 n-2 n2号环都已被拆下,要拆 n − 1 n-1 n1号环就要先装上 n − 2 n-2 n2号环,继续推得要装上 n − 3 n-3 n3号环,以此类推,要将 1 1 1 n − 2 n-2 n2号环全部装上,然后就变成了一个 n − 1 n-1 n1连环。

重新理一下:

  • 拆掉 1 1 1 n − 2 n-2 n2号环,装卸次数为 f n − 2 f_{n-2} fn2
  • 拆掉 n n n号环,装卸次数为 1 1 1
  • 装上 1 1 1 n − 2 n-2 n2号环,装卸次数为 f n − 2 f_{n-2} fn2
  • 拆掉 1 1 1 n − 1 n-1 n1号环,装卸次数为 f n − 1 f_{n-1} fn1

由此可以得到递推式 f n = f n − 1 + 2 f n − 2 + 1 f_n=f_{n-1}+2f_{n-2}+1 fn=fn1+2fn2+1

g n = f n + f n − 1 + 1 g_n=f_n+f_{n-1}+1 gn=fn+fn1+1 g 1 = 2 g_1=2 g1=2,将 f n f_n fn按上述递推式拆开可得 g n = f n + f n − 1 + 1 = 2 ( f n − 1 + f n − 2 + 1 ) = 2 g n − 1 g_n=f_n+f_{n-1}+1=2(f_{n-1}+f_{n-2}+1)=2g_{n-1} gn=fn+fn1+1=2(fn1+fn2+1)=2gn1,即 g n = 2 n g_n=2^n gn=2n

再来推 f n f_n fn。由 f n = f n − 1 + 2 f n − 2 + 1 f_n=f_{n-1}+2f_{n-2}+1 fn=fn1+2fn2+1可得 f n = f n − 2 + g n − 1 = f n − 2 + 2 n − 1 f_n=f_{n-2}+g_{n-1}=f_{n-2}+2^{n-1} fn=fn2+gn1=fn2+2n1

如果 n n n为偶数,令 n = 2 k n=2k n=2k,则 f 2 k = f 2 k − 2 + 2 2 k − 1 = 2 2 k − 1 + 2 2 k − 3 + ⋯ + 2 1 f_{2k}=f_{2k-2}+2^{2k-1}=2^{2k-1}+2^{2k-3}+\cdots+2^1 f2k=f2k2+22k1=22k1+22k3++21

由等比数列可得 f 2 k = 2 2 k + 1 − 2 3 f_{2k}=\dfrac{2^{2k+1}-2}{3} f2k=322k+12

如果 n n n为奇数,令 n = 2 k + 1 n=2k+1 n=2k+1,则 g 2 k + 1 = f 2 k + 1 + f 2 k + 1 g_{2k+1}=f_{2k+1}+f_{2k}+1 g2k+1=f2k+1+f2k+1 f 2 k + 1 = g 2 k + 1 − f 2 k − 1 = 2 2 k + 1 × 3 − 2 2 k + 1 + 2 − 3 3 f_{2k+1}=g_{2k+1}-f_{2k}-1=\dfrac{2^{2k+1}\times 3-2^{2k+1}+2-3}{3} f2k+1=g2k+1f2k1=322k+1×322k+1+23

整理得 f 2 k + 1 = 2 k + 2 − 1 3 f_{2k+1}=\dfrac{2^{k+2}-1}{3} f2k+1=32k+21

综上所述, f n = ⌊ 2 n + 1 3 ⌋ f_n=\lfloor\dfrac{2^{n+1}}{3}\rfloor fn=32n+1

我们发现要用高精度,而且一次乘法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),用快速幂的话总时间复杂度为 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)。考虑用 F F T FFT FFT优化高精度乘法,则总时间复杂度为 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

code

#include<bits/stdc++.h>
using namespace std;
const int N=100000;
const double pi=acos(-1.0);
struct big{
	int len,a[N+5];
	friend big operator/(const big ax,const int bx){
		big cx;
		cx.len=ax.len;
		int now=0;
		for(int i=ax.len;i>=0;i--){
			cx.a[i]=(now*10+ax.a[i])/3;
			now=(now*10+ax.a[i])%3;
		}
		while(cx.len&&cx.a[cx.len]==0) --cx.len;
		return cx;
	}
}ans;
struct cp{
	double a,b;
	friend cp operator+(const cp ax,const cp bx){
		return (cp){ax.a+bx.a,ax.b+bx.b};
	}
	friend cp operator-(const cp ax,const cp bx){
		return (cp){ax.a-bx.a,ax.b-bx.b};
	}
	friend cp operator*(const cp ax,const cp bx){
		return (cp){ax.a*bx.a-ax.b*bx.b,ax.a*bx.b+ax.b*bx.a};
	}
}w,wn,a1[N+5],a2[N+5];
void ch(cp *a,int l){
	for(int i=1,j=l/2,k;i<l-1;i++){
		if(i<j) swap(a[i],a[j]);
		k=l/2;
		while(j>=k){
			j-=k;k>>=1;
		}
		j+=k;
	}
}
void fft(cp *a,int l,int fl){
	ch(a,l);
	for(int i=2;i<=l;i<<=1){
		wn=(cp){cos(fl*2*pi/i),sin(fl*2*pi/i)};
		for(int j=0;j<l;j+=i){
			w=(cp){1,0};
			for(int k=j;k<j+i/2;k++,w=w*wn){
				cp t=a[k],u=w*a[k+i/2];
				a[k]=t+u;
				a[k+i/2]=t-u;
			}
		}
	}
}
big tim(const big &ax,const big &bx){
	int l1=ax.len+1,l2=bx.len+1;
	int l=1;
	while(l<l1+l2) l<<=1;
	for(int i=0;i<l1;i++) a1[i]=(cp){(double)ax.a[i],0};
	for(int i=l1;i<l;i++) a1[i]=(cp){0,0};
	for(int i=0;i<l2;i++) a2[i]=(cp){(double)bx.a[i],0};
	for(int i=l2;i<l;i++) a2[i]=(cp){0,0};
	fft(a1,l,1);fft(a2,l,1);
	for(int i=0;i<l;i++) a1[i]=a1[i]*a2[i];
	fft(a1,l,-1);
	big re;
	re.len=ax.len+bx.len;
	for(int i=0;i<l1+l2-1;i++) re.a[i]=(int)(a1[i].a/l+0.5);
	for(int i=0;i<=re.len;i++){
		if(re.a[i]>=10){
			if(i==re.len) re.a[++re.len]=0;
			re.a[i+1]+=re.a[i]/10;
			re.a[i]%=10;
		}
	}
	while(re.len&&re.a[re.len]==0) --re.len;
	return re;
}
big gt(int t){
	big re;
	re.a[re.len=0]=t;
	return re;
}
big mi(big bs,int t){
	big re=gt(1);
	while(t){
		if(t&1) re=tim(re,bs);
		t>>=1;bs=tim(bs,bs);
	}
	return re;
}
void print(){
	for(int i=ans.len;i>=0;i--){
		printf("%d",ans.a[i]);
	}
	printf("\n");
}
int main()
{
	int T,n;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		ans=mi(gt(2),n+1)/3;
		print();
	}
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值