若果有一组硬币,(假定有十个),每个硬币只有两个面,正面用以表示,反面用零表示.
给定目标(初始状态)1111100000 正正正正正反反反反反
(目标状态) 1000011101 正反反反反正正正反正
规定每次只可以翻转相邻的两个硬币(他们各自成为原来的对立面)
问题,至少用多少次就可以达到目的.
将每一种状态标记为,每一次的硬币翻动(1-9)状态i,都会使,当前状态变为另一种状态状态i+1,如果在状态
i+1的位置,翻动和相应的位置,则会,回到状态i,因此判断是(状态与状态之间的转换)可以互相进行.以此判断图的搜索.
此处采索,
方法一
分析.
每个硬币翻转后都会(且必须)影响一硬币,硬币个数大于一
考虑每次翻转都会有两个硬币翻转到相反状态,如果目标与初始有基数个不相同,就不能达到目标状态.
我们尽可能的是硬币当前硬币接近给定状态
因此从左向右,如果i号硬币与目标相同,跳过,否则翻转i,i+1号硬币.(i<n),翻转次数加一.
当到i==n是如果i号与目标相同则得到翻转次数.
否则不能通过有该翻转规则得到,因为有一个不同(1,是基数而该规则是,每次改变两个硬币).
问题是如何证明最有性.
一个硬币如果被翻转偶数次相当与没有翻转
若被翻转多次,假设硬币i被翻转n次,相当于n%2次
那麽i-1,被翻转x次(相当于x%2),i+1被翻转了n-x次((n-x)%2)
x为偶数,n-x为奇数 n为奇数 翻转i和i+1
x为偶数 n-x为偶数 n也为偶数 相当于没有任何硬币被翻转
x为奇数 n-x为偶数 n为基数 相当于值翻转i-1,i
x为基数n-x为基数翻转了i-1,i+1.
通过上述四种情况我们发现可以对一个硬币有两种操作
1.翻转左右各一次(多次效果相同次数增多不是问题的解)使得相距一个的硬币翻转
2.翻转左边一次或者右边一次,效果都是使得相邻的硬币发生翻转
以下面情况举例说明2覆盖1
a.假如 要是0 0 0 翻转到 1 0 1
第一种翻转方法翻转两边使得达到结果次数为2
第二种方法翻转1,2,使得达到效果结果次数为2
b.要是0 0 0 变为 1 1 0
第一种方式不能达到,而第二种方式只要翻转1,影响2(翻转2,影响1)即可结果为1
由情况a,b可以看出要改变一个硬币只需改变他和他相邻的硬币即可.
相邻的有两边,那麽要改变硬币i,到底要硬向那边,可以从第一个开始来判断是否i号需要被改变,那麽左边的都是完成改变的,因此只需顾虑右边就好了.
那可能会有疑问了,会不会从右边开始结果更少呢?
从两边开始结果一样的
很明白的知道,所有翻转的点是与目标不同的点.现在有m个(m是偶数)因为已经证明结论基数个不相同硬币是不可能相邻翻转来改变是指相同,此时设有0<=i<j<=n;
a[i],a[j]都与目标不相同,此时从左向右还是从右翻转都是j-i次,那麽原硬币中有m个与最终结果不相同,此时可将这m开做k(o..m/2)的ik..jk.不相同的亮亮对,他们翻转操作次数
都是k(1...m/2) 求和ij-ik,因此偶数(可通过翻转到达的情况是从左到右从右到左是相同的).
例如记i号硬币起始情况与最终情况相同为1,否则为零那麽对下面的情况用上面的结论来计算结果.
1 0 1 0 0 0 1 0 1 0
从左向右(4-2)+ (6-5)+ (10 -8)= 5次
从右向左 (10-8) + (6-5) + (4-2) =5次
上面规律应征名,所有这杨m为偶数结果,从左向右,从右向左都相同。
下面解法即从左向右.
#include <stdio.h>
int main(){
int count=0,i;
int a[10];
int b[10];
for(i=0;i<10;i++){
scanf("%d",&a[i]);
}
for(i=0;i<10;i++){
scanf("%d",&b[i]);
}
for(i=0;i<9;i++){
if(a[i]!=b[i]){
a[i]=!a[i];
a[i+1]=!a[i+1];
count++;
}
}
printf("%d\n",a[i]==b[i]?count:-1);
return 0;
}
方法二
采取bfs,搜素状态树即可.
要点两个:
1.状态的保存(判重复节点).若果节点重复则不应打开(去遍历他所对应的节点间)。
2.状态的表示,该题目的状态的表示,使用简单的哈希对应关系.
#include <iostream>
#include <cstring>
using namespace std;
const int size = 1000000+100;
typedef int state[10];
state st[size];
int vis[size]={0};
int dis[size]={0};
int getkey(state &s)
{
int i,adder=0;
for(i=0;i<10;i++){
adder=adder*2+s[i];
}
return adder;
}
int isvis(state &s){
int key=getkey(s);
if(vis[key])
return 1;
else {
vis[key]=1;
return 0;
}
}
int bfs(int front,int rear,state goal)
{
int i,j,k;
while(front!=rear){
state &s=st[front];
if(memcmp(goal,s,sizeof(s))==0)
return dis[front];
else{
for(i=0;i<9;i++){
state &t=st[rear];
memcpy(&t,&s,sizeof(s));
t[i]=!t[i];
t[i+1]=!t[i+1];
dis[rear]=dis[front]+1;
if(!isvis(t))
rear++;
}
}
front++;
}
return -1;
}
int main()
{
int front=1,rear=2,i,j,k;
state goal;
int distance=-1;
for(i=0;i<10;i++){
cin>>st[front][i];
}
for(i=0;i<10;i++){
cin>>goal[i];
}
distance=bfs(front,rear,goal);
cout<<distance<<endl;
return 0;
}
给出10个硬币初始状态,和最终状态得到最短反转步数.