[CQOI2018]九连环

本文介绍了CQOI2018中九连环问题的解题思路,通过分析4连环的拆除规律,提炼出递推公式dp[i]=2⋅dp[i−2]+dp[i−1]+1。将解法转化为二进制形式,并发现相邻两项之和为2的i次幂加1。最后,利用FFT优化进行高精度计算,强调了在实现过程中的注意事项,如数组清零和复用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、题目

点此看题

二、解法

根据题目中 4 4 4连环的解释,你可以发现拆除 4 4 4连环满足如下规律:

  • 拆掉一个 2 2 2连环,使得原环变成 1100 1100 1100
  • 拿掉最左边的 1 1 1
  • 加二连环使其变为 0111 0111 0111
  • 拆三连环

这就完成了一个问题的转化,由于拆和加环效果相同,我们定义 d p i dp_i dpi为拆掉 i i i连环需要的步数,则有递推式:
d p [ i ] = 2 ⋅ d p [ i − 2 ] + d p [ i − 1 ] + 1 dp[i]=2\cdot dp[i-2]+dp[i-1]+1 dp[i]=2dp[i2]+dp[i1]+1可以把答案写成二进制的形式:1 10 101 1010 10101 . . . . . . ...... ......

可以发现相邻两个二进制数的和是 1111.. 1111.. 1111..的形式,所以 d p [ i ] + d p [ i + 1 ] = 2 i + 1 dp[i]+dp[i+1]=2^{i+1} dp[i]+dp[i+1]=2i+1,结合 d p [ n ] = 2 d p [ n − 1 ] + ( n & 1 ) ? 1 : 0 dp[n]=2dp[n−1]+(n\&1)?1:0 dp[n]=2dp[n1]+(n&1)?1:0(打表规律),可以发现答案是 2 i + 1 3 \frac{2^{i+1}}{3} 32i+1

这样问题就转化成了高精度,可以用 FFT \text{FFT} FFT优化,下面写出一些注意事项:

  • 清零最重要,每个地方每个数组及时清零
  • 不要把数组开在函数里面,开在函数外面反复利用
#include <cstdio>
#include <vector>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN = 100005;
const double pi = acos(-1.0);
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,T;
struct complex
{
	double x,y;
	complex() {x=y=0;}
	complex(double X,double Y) : x(X) , y(Y) {}
	complex operator + (const complex &R) const {return complex(x+R.x,y+R.y);}
	complex operator - (const complex &R) const {return complex(x-R.x,y-R.y);}
	complex operator * (const complex &R) const {return complex(x*R.x-y*R.y,x*R.y+y*R.x);} 
}A[MAXN],B[MAXN];
void FFT(int len,complex *a,int flg)
{
	if(len==1) return ;
	complex a1[len>>1],a2[len>>1];
	for(int i=0;i<len;i+=2) a1[i>>1]=a[i],a2[i>>1]=a[i+1];
	FFT(len>>1,a1,flg);FFT(len>>1,a2,flg);
	const complex w=complex(cos(pi*2.0/len),sin(pi*2.0/len)*flg);
	complex k=complex(1,0);len>>=1;
	for(int i=0;i<len;i++,k=k*w)
	{
		a[i]=a1[i]+k*a2[i];
		a[i+len]=a1[i]-k*a2[i];
	}
}
struct bignum
{
	int n,a[MAXN];
	bignum() {memset(a,0,sizeof a);n=1;}
	bignum(int x)
	{
		memset(a,0,sizeof a);n=0;
		if(!x) {n=1;return ;}
		while(x) a[n++]=x%10,x/=10;
	}
	void print()
	{
		for(int i=n-1;i>=0;i--)
			printf("%d",a[i]);
		puts("");
	}
	void operator /= (int x)
	{
		int t=0,len=0;
		for(int i=n-1;i>=0;i--)
		{
			t=t*10+a[i];
			a[i]=t/x;t%=x;
			if(!len && a[i]) len=i+1;
		}
		n=max(len,1);
	}
	void operator *= (bignum b)
	{
		int len=1;while(len<=n+b.n) len<<=1;
		for(int i=0;i<len;i++) A[i]=B[i]=complex(0,0);
		for(int i=0;i<n;i++) A[i]=complex(a[i],0);
		for(int i=0;i<b.n;i++) B[i]=complex(b.a[i],0);
		FFT(len,A,1);FFT(len,B,1);
		for(int i=0;i<len;i++) A[i]=A[i]*B[i];
		FFT(len,A,-1);
		for(int i=0;i<n+b.n;i++)
			a[i]=0;
		for(int i=0;i<n+b.n;i++)
		{
			a[i]+=(int)(A[i].x/len+0.5);
			if(a[i]>=10)
				a[i+1]+=a[i]/10,a[i]%=10;
		}
		for(int i=n+b.n-1;i>=0;i--)
			if(a[i])
			{
				n=i+1;
				break;
			}
	}
}a,r;
int main()
{
	T=read();
	while(T--)
	{
		n=read()+1;
		a=bignum(2),r=bignum(1);
		while(n>0)
		{
			if(n&1) r*=a;
			a*=a;
			n>>=1;
		}
		r/=3;
		r.print();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值