0x02 递推与递归学习笔记

费解的机关

题目:https://www.acwing.com/problem/content/97/

思路: 

其实当我看到这道题的时候,真的是二丈和尚摸不到脑袋,完全想不出思路来。然后看了一下答案,我也是更加迷惑,总感觉无法理解答案的思路,为什么只要固定第一行就可以了呢?然后再仔细想了一下,我才想通。

每个点作为中心点被点的次数不能超过一次,你想如果某个点作为中心点被点了两次,那不相当于没点吗?还白白浪费一次 。所以这个问题其实可以看作一个“排列”问题,就是5*5的点中有哪些作为中心点被点一次后能让灯全部都亮起来,而且点的次数最短。所以再看看书上给的答案,固定第一行:就相当于给定了第一行中点与不点的情况。进而导致,如果在固定第一行之后,如果第一行还有0的话,那么只能通过 点第二行的灯才能将第一行的0变成1,如果第一行的第k号灯已经是1了,那么第二行的第k号灯就没必要点了,这样第二行点的情况也被固定住了,进而是第3、4、5……。最后通过判断最后一行是否全为1判断此种方法是否可行。

#include<iostream>
using namespace std;
const int maxn=10;
int data1[maxn][maxn],temp[maxn][maxn],ans=7;
int dis[5][2]={0,0,1,0,-1,0,0,1,0,-1};
bool judge()
{
	for(int j=1;j<=5;j++)
	if(temp[5][j]==0)return false;
	return true;
}
void solve(int num,int step)
{
	if(step>=ans)return;//step需要小于ans
	if(num>5)
	{
		for(int i=1;i<=5;i++)
		{
			for(int j=1;j<=5;j++)
			temp[i][j]=data1[i][j];
		} 
		int s=step,i1,j1;
		for(int i=1;i<=4;i++)
		{
			for(int j=1;j<=5;j++)
			{
				if(!temp[i][j])
				{
					++s;
					i1=i+1;j1=j;
					for(int k=0;k<=4;k++)
					temp[i1+dis[k][0]][j1+dis[k][1]]^=1; 
				}
			}
		}
		if(judge()&&ans>s)ans=s;
		return;
	}
	for(int k=0;k<=4;k++)
	data1[1+dis[k][0]][num+dis[k][1]]^=1;
	solve(num+1,step+1);
	for(int k=0;k<=4;k++)
	data1[1+dis[k][0]][num+dis[k][1]]^=1;
	solve(num+1,step);
	return;
}
int main()
{
	int n; 
	scanf("%d",&n);
	while(n--)
	{
		char a;
		for(int i=1;i<=5;i++)
		{
			for(int j=1;j<=5;j++)
			{
				cin>>a;
				data1[i][j]=a-'0';
			}
		}
		ans=7;
		solve(1,0);
		if(ans==7)printf("%d\n",-1);
		else printf("%d\n",ans);
	}
 } 

Strange Tower of Hanoi

题目:https://www.acwing.com/problem/content/98/ 

思路:

写完这道题,你会感受到递推的魅力。

先说一下我的错误思路,这个呢只是针对于我自己以后复习用。看到这道题其实还没有想到思路,我就开始了混乱猜测了,先是把这道题当普通的三个棍子的汉诺塔来做,结果肯定是错的。然后我又进行了多次的乱搞。当我冷静下来时,我还是联想三个棍子的汉诺塔,先把上面的n-1个盘子放到空闲的那根棍子上,再把第n个盘子放到目标棍子上,然后再把n-1个盘子整体放到目标棍子上。那么这道题有四根棍子,是不是也是这个思路,只是方式不一样,我先开始了对半分的尝试,没错就是先把n-1个盘子对半分,分别放到闲置的两根棍子上,然后再把第n个盘子放到目标棍子上,然后再把另外两根棍子上的盘子放到目标棍子上。但还是错,那我没办法了,那我就只能蠢蠢的暴力了,先把前面a个盘子放到某一根棍子上,相当于在四根棍子的辅助下放到目标棍子上,移动次数设为four[a]。再把后面的b个盘子放到某一根棍子上,相当于在三根棍子的辅助下放到目标棍子上,移动次数设为three[b],然后再把第n个盘子放到目标棍子上,再把那b个盘子放到目标棍子上,次数为three[b],再把a个盘子放到目标棍子上,次数为four[a]。

通过以上的思路历程,得到ans=min(1+2*(four[a]+three[b]))。其中three[b]=2*three[b-1]+1

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=20;
int three[maxn],four[maxn];
int main()
{
	memset(four,0x3f,sizeof(four));
	three[0]=0;four[0]=0;
	three[1]=four[1]=1;
	printf("1\n");
	for(int i=2;i<=12;i++)
	{
		three[i]=2*three[i-1]+1;
		for(int j=1;j<i;j++)
		four[i]=min(four[i],2*three[i-1-j]+2*four[j]+1);
		printf("%d\n",four[i]);
	}
}

Sumdiv

题目:https://www.acwing.com/problem/content/99/

思路:

没错我一开始还是不会,所以只能看答案了。

我一开始先用的质数筛,先找出质数先,然后再分解A,但是这种方法超空间,所以我去搜了答案。没错答案跟我的打法不一样,人家根本没有找质数的操作。分解A的操作是这样一段代码。

ll solve(ll a,ll b)
{
	ll ans=1;
	ll c,p;
	for(int i=2;i*i<=a;i++)
	{
		if(a%i==0)
		{
			p=i;
			c=0;
			while(a%i==0)
			{
				a/=i;
				++c;
			}
			ans=(ans*sum(p%m,c*b))%m;
		 } 
	}
	if(a>1)ans=(ans*sum(a,b))%m;
	return ans;
}

他这个就比我的好多了(这个是自己写的,但是跟他的打法是一致的)。

首先他找到的约数都是质数,为什么,你想如果这个算法找到了一个合数a1,易得a1肯定能被某个比a小的质数b除断。可以看一下这段代码内部的while循环,这个循环保证了如果一个数可以除断a,那么会一直与之相除直至无法除断,所以找到的约数都是质数。

其次我觉得那个for循环的条件i*i<=a也比较精妙,你想想,如果在i*i<=a中没有数能除断a,那么可定不存在i*i>a且a%i==0的数,这样能少一点循环的次数

代码

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
typedef long long ll;
const int m=9901;
ll solve(ll a,ll b);
ll sum(ll p,ll c);
ll my_pow(ll a,ll b);
int main()
{
	ll a,b;
	scanf("%lld%lld",&a,&b);
	ll ans;
	if(a==0)ans=0;
	else if(b==0)ans=1;
	else ans=solve(a,b);
	printf("%lld\n",ans);
}
ll my_pow(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b%2==1)
		ans=(ans*a)%m;
		b/=2;
		a=(a*a)%m;
	}
	return ans;
}
ll sum(ll p,ll c)
{
	if(c==0)return 1;
	if(c%2)
	{
		ll t1=my_pow(p,(c+1)/2);
		ll t2=sum(p,(c-1)/2);	
		return (((1+t1)%m)*t2)%m;
	}
	else
	{
		ll t1=my_pow(p,c/2);
		ll t2=sum(p,c/2-1);
		ll t3=my_pow(p,c);
		return ((((1+t1)%m)*t2)%m+t3)%m;
	}
}
ll solve(ll a,ll b)
{
	ll ans=1;
	ll c,p;
	for(int i=2;i*i<=a;i++)
	{
		if(a%i==0)
		{
			p=i;
			c=0;
			while(a%i==0)
			{
				a/=i;
				++c;
			}
			ans=(ans*sum(p%m,c*b))%m;
		 } 
	}
	if(a>1)ans=(ans*sum(a,b))%m;
	return ans;
}

 总结

继续加油

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值