题目描述
FJ的奶牛想要快速计算整数P的幂 (1 <= P <=20,000),它们需要你的帮助。因为计算极大数的幂,所以它们同一时间仅能使用2个存储器,每个存储器可记录某个结果值。 第一件工作是初始化存储器内的值一个为底数x, 另一个为1。 奶牛可以相乘或相除2个存储器中的值,并把结果存在其中某个存储器内,但所有存储的结果必须是整数。 例如, 如果他们想计算x^31, 一种计算方法是:
WV1 WV2
开始: x 1
存储器1和存储器1相乘,结果存于存储器2: x x^2
存储器2和存储器2相乘,结果存于存储器2: x x^4
存储器2和存储器2相乘,结果存于存储器2: x x^8
存储器2和存储器2相乘,结果存于存储器2: x x^16
存储器2和存储器2相乘,结果存于存储器2: x x^32
存储器2除以存储器1,结果存于存储器2: x x^31
因此, x^31可以通过6次计算得出。给出要计算的幂次,要求求出最少需要几次计算。
输入格式
仅一个整数: P。
输出格式
仅一个整数:最少计算次数。
样例数据
input
31
output
6
题目大意
有两个数,分别是 和 ;每次选取其中两个相同或不同的数相加或相减,每次替换掉其中的一个数,并在保证每一个数都是非负数的前提下不断进行这样的操作,问至少要操作多少次才能使其中的一个数变成n。
题解
这道题由于边权为 ,可以选择使用正常的广度优先搜索算法;但是搜索状态有 ,因此我们需要用其他算法来解决此题。
我们可以使用启发式 算法来解决,可以设计估价函数为:
- 由于自加最快,不断对最大数进行*2操作即可。
- 一定可以保证优于最优策略:如果步数 不用说,直接可以到达最优决策。如果操作步数 ,要么通过若干次相减得到,要么直接无解,但是一定可以小于最优决策。
但是这样的 算法仍然有超时的可能,我们还需要对此进行一系列的剪枝,对于每一个状态 ,表示两个数分别为 且满足 ,操作步数为 ,则有:
- 如果 和 是负数,则是一组非法解。
- 如果 且 ,则不论如何都不能达到 ( 无法通过 变小,只能通过自减变成 ;若如此,则无法通过自身与 变大达到 )。
- 如果
,则无法通过加减操作得到最优解(
此时需要意会)。 - 如果 ,则相当于状态 的自加自减操作,是一种等效的状态,因此可以剪枝。
- 显然对于每一个状态到需要用 算法解决;如果当前状态的步数 该状态的最优步数, 则剪枝。
注意:
- 对于 算法,要设计两个关键字;第一关键字为深度+估价函数,第二关键字为估价函数。因为第二关键字越少,基于底层的搜索层数也越少,可以在很大情况下减少搜索树的规模。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1000000;
const int V = 100000;
int n,tot,ans;
int Link[V],X[N],Y[N],Next[N],v[N];
struct node
{
int x,y,v;
int f (void)
{
int tx = x;
int cnt = 0;
while (tx<n) tx<<=1, cnt ++;
return cnt;
}
friend bool operator < (node a,node b)
{
//if (a.v+a.f() ^ b.v+b.f())
return a.v+a.f() > b.v+b.f();
//else
// return a.f() > b.f();
}
};
priority_queue < node > q;
inline int Hash (int x,int y)
{
int num = x*47+y*991;
return num%99991;
}
void add (int x,int y,int num)
{
tot ++;
Next[tot] = Link[Hash(x,y)];
X[tot] = x;
Y[tot] = y;
v[tot] = num;
Link[Hash(x,y)] = tot;
}
int ask (int x,int y)
{
for (int i=Link[Hash(x,y)];i;i=Next[i])
if (x == X[i] && y == Y[i])
return v[i];
return -1;
}
pair<int,int> New(int x,int y,int num)
{
if (num == 0) return make_pair(x+x,y);
if (num == 1) return make_pair(x+x,x);
if (num == 2) return make_pair(y+y,y);
if (num == 3) return make_pair(max(y+y,x),min(y+y,x));
if (num == 4) return make_pair(x+y,y);
if (num == 5) return make_pair(x+y,x);
if (num == 6) return make_pair(x-y,x);
if (num == 7) return make_pair(max(x-y,y),min(x-y,y));
if (num == 8) return make_pair(y,0);
if (num == 9) return make_pair(x,0);
}
int gcd(int a,int b)
{
if (b == 0) return a;
return gcd(b,a%b);
}
int main(void)
{
freopen("power.in","r",stdin);
freopen("power.out","w",stdout);
tot = 0;
cin>>n;
add(1,0,0);
q.push(node{1,0,0});
while (q.size())
{
node top=q.top();
q.pop();
if (top.x == n || top.y == n)
{
ans = top.v;
break;
}
if (top.v > ask(top.x,top.y)) continue;
for (int i=0;i<10;++i)
{
pair<int,int> Next = New(top.x,top.y,i);
int nx = Next.first;
int ny = Next.second;
if (nx < 0 || ny < 0) continue;
if (nx == ny) continue;
//等效状态
if (ask(nx,ny) ^ -1 && ask(nx,ny) < top.v+1) continue;
//相同状态下访问步数少
if (n % gcd(nx,ny)) continue;
//无解
if (nx > n && ny == 0) continue;
//无解
add(nx,ny,top.v+1);
q.push(node{nx,ny,top.v+1});
}
}
cout<<ans<<endl;
return 0;
}