整数变换问题
Time Limit: 1000 ms Memory Limit: 65536 KiB
Problem Description
整数变换问题。关于整数i的变换f和g定义如下:f(i)=3i;
试设计一个算法,对于给定的2 个整数n和m,用最少的f和g变换次数将n变换为m。例如,可以将整数15用4 次变换将它变换为整数4:4=gfgg(15)。当整数n不可能变换为整数m时,算法应如何处理?
对任意给定的整数n和m,计算将整数n变换为整数m所需要的最少变换次数。
Input
输入数据的第一行有2 个正整数n和m。n≤100000,m≤1000000000。
Output
将计算出的最少变换次数以及相应的变换序列输出。第一行是最少变换次数。第2 行是相应的变换序列。
Sample Input
15 4
Sample Output
4
gfgg
二、问题分析
A.题目分析:
观察f和g两个函数发现,f总是使得i变大,g总是使得i变小。因此我们在决定让x执行哪个函数之前必须先判断i和目标值m之间的大小关系。如果x>m,就让其执行g函数;反之,执行f函数。
这道题目有两种情况,一种是有解,即n可以通过函数变换成m;另一种是无解,即n无法通过函数变换成m。那我们如何去判断某一个样例输入是属于哪种情况呢?
第一种情况比较容易,即我们只需要判断最后的i是否等于m即可。如果i等于m,那么说明n已经被变换成m了,递归返回。
第二种情况的话需要我们找下规律。假设我们的输入n=9,m=5。
n>m,执行g,n=[9/2]=4
n<m,执行f,n=3*4=12
n>m,执行g,n=[12/2]=6
n>m,执行f,n=[6/2]=3
n<m,执行g,n=3*3=9
n>m,执行f,n=[9/2]=4
我们会发现如果n的值陷入了一个重复的循环。也就是如果在我们递归的过程,出现了我们前面计算过的元素,那就说明n是无法转换成m的。
B.算法设计:
用回溯法解决整数变换问题,显然用子集树是最合适的。该子集树如下
我们并不知道该树有多少层,因为对于不同整数,它们之间所需要的变换次数也是不同并且不确定的。那么我们这个时候不能像以往的回溯法,用进行的层数来作为返回条件。
根据我们前面的题目分析,可以知道我们返回条件有两个,一个是i等于了m,也就是if(i==m);另一个是出现了重复的数字。这个返回条件我们需要构造一个test方法来判断。这个test方法主要就是在下一层走之前我们先对这个参数和以往的参数进行一个比较,如果这个参数和以往的某个参数相等,说明它已经出现过了,那么我们就让回溯递归得函数返回0,说明n是无法转换为m的。
剪枝条件:
显示约束:如果x>m,我们就剪掉它的左子树;如果x<m,我们就剪掉它的右子树;
隐式约束:如果我们在某次计算的过程中发现当前的计算次数已经大于或等于最少计算次数了,那么我们就剪掉这个分支。
#include<bits/stdc++.h>
using namespace std;
vector<char>s;
int l;
int pre(int n,int x)
{
if(x==0)
{
return 3*n;
}
else
{
return n/2;
}
}
bool dfs(int step,int n,int m)
{
int num;
if(step>l)
{
return false;
}
num=n;
for(int i=0; i<2; i++)
{
num=pre(n,i);
if(num==m||dfs(step+1,num,m))
{
if(i==0)
{
s.push_back('f');
}
else
{
s.push_back('g');
}
return true;
}
}
return false;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
l=1;
while(!dfs(1,n,m))
{
l++;
}
printf("%d\n",l);
for(int i=0; i<(int)s.size(); i++)
{
printf("%c",s[i]);
}
printf("\n");
return 0;
}