HDU4026Unlock the Cell Phone
现代手机都有解锁模式,比如在一个3*3的键盘上,你可以选定一个起始点,点上去然后滑动到它相邻的点上。比如起始为1可以滑动到2,4,5,6,8上,因为1到这些点之间都没有其他点。但是你不能直接从1滑动到7或3,因为2或4你还没走过。当你走到了一个点之后,你就能从它上面划过去了。如下面第3个图1->5->9->6->4,因为5已经走过了,所以可以从6滑到4.
现在有一个特殊的手机,它有3种点:0号普通点,能走过上去且能从上面滑过.1号被禁止的点,不能走上且也不能从上面滑过.2号不活跃的点:你不能走上去但是能从上面滑过.
每个点只能走上去1次,现在问你有多少种解锁方式走过了所有的0号点.
输入:包含多组实例.每个实例第一行为n和m,表示矩阵的大小,接下来是一个n*m的矩阵,包含数字0,1,2.对应上面的0号,1号,2号点.其中0号点的数目在[1,16]之间.
输出:不同解锁模式的总数.
分析:这道题目和TSP问题很类似,需要走完每个0号点,且每个0号点都只能被走1次.令d[i][S]=x表示当前在第i个0号点,且走过了集合S中的所有0号点时总共的行走方式数为x.
d[i][S]= sum{ d[j][S-{i}] } 如果在状态S-{i}下,从j到i之间不会经过一个1号点或没走过的0号点,那么可以转移状态.
初值:d[i][{i}]=1
复杂度分析16*(1<<16)=100W左右.
超时代码:测试结果正确但是超时.(未用long long表示d,会溢出)
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//下面是二维几何************************************************
const double eps=1e-10;
struct Point
{
double x;
double y;
Point(double _x=0,double _y=0){x=_x;y=_y;}
Point operator +(const Point &b)const
{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const
{
return Point(x-b.x,y-b.y);
}
Point operator *(double p)const
{
return Point(x*p,y*p);
}
Point operator /(double p)const
{
return Point(x/p,y/p);
}
};
typedef Point Vector;
int dcmp(double x)
{
if(fabs(x)<eps)
return 0;
else
return x<0?-1:1;
}
bool operator == (const Point& a,constPoint &b)
{
return dcmp(a.x-b.x)==0 && dcmp(a.y-b.y)==0;
}
double Dot(Vector A,Vector B)
{
returnA.x*B.x + A.y*B.y;
}
double Length(Vector A)
{
return sqrt(Dot(A,A));
}
double Angle(Vector A,Vector B)
{
return acos( Dot(A,B)/Length(A)/Length(B) );
}
double Cross(Vector A,Vector B)
{
return A.x*B.y-A.y*B.x;
}
double Area2(Point A,Point B,Point C)
{
return Cross(B-A,C-A);
}
bool OnSegment(Point p,Point a1,Point a2)
{
return dcmp( Cross(a1-p,a2-p) )==0 && dcmp( Dot(a1-p,a2-p))<0;
}
//上面是二维几何************************************************
int n,m;
int d[16][1<<16];
int g[10][10];//保存初始矩阵
int cnt;//共有cnt个0号点
int x[20],y[20];//x[i]和y[i]表示第i个0号点的x与y坐标
int get_id(int px,int py)//得到对应0号点的编号
{
for(int i=0;i<cnt;i++)
if(x[i]==px&&y[i]==py)
return i;
return -100000;
}
bool has_way(int j,int i,int S1)//判断在状态S1下从j到i之间是否有路
{ //即j到i之间是否有1号点或没走过的0号点
for(int px=0;px<n;px++)
for(int py=0;py<m;py++)
{
if(g[px][py]==2)//可以滑过不可走上点
continue;
else if(g[px][py]==0)//普通点
{
int k = get_id(px,py);
if(k==i || k==j)continue;
if(S1&(1<<k))//第k个0号点已经被走过
continue;
}
double p1,p2,a1,a2,b1,b2;//判断当前点是否在线段i和j之间
p1=(double)px;
p2=(double)py;
a1=(double)x[i];
a2=(double)y[i];
b1=(double)x[j];
b2=(double)y[j];
Point P(p1,p2),A(a1,a2),B(b1,b2);
if(OnSegment(P,B,A))
return false;
}
return true;
}
int dp(int i,int S)
{
if(d[i][S]>=0)return d[i][S];
int &ans = d[i][S];
int S1 = S^(1<<i);
ans = 0;
for(int j=0;j<cnt;j++)
if(j!=i&& S1&(1<<j) && has_way(j,i,S1))
ans += dp(j,S1);
return ans;
}
int main()
{
while(scanf("%d%d",&n,&m)==2)
{
cnt= 0;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
scanf("%d",&g[i][j]);
if(g[i][j]==0)
{
x[cnt]=i;
y[cnt++]=j;
}
}
memset(d,-1,sizeof(d));//非法状态无法到达,直接初始为-1
for(int i=0;i<cnt;i++)
d[i][1<<i]=1;
int sum=0;
int all=(1<<cnt)-1;
for(int i=0;i<cnt;i++)
sum += dp(i,all);
printf("%d\n",sum);
}
return 0;
}
上面代码超时的关键在于判断在状态S1下i与j之间是否有一条路?我上面用的方法是25个点,依次用计算机几何的方法判断任意一个不可跨过的点是否在线段i-j之间.然后就超时了.
解法二:其实这里面每个点的坐标都是在一个矩阵中的,判断从i到j有没有其他点,只要从i向j移动即可,每次移动一小步,最多移动4步(想想为什么?).如果移动到了一个非法点上,那么i与j就没有路,否则就有路.
那么什么叫每次移动一小步呢?假设i为(0,0),j为(2,4),那么从i到j的向量是(2,4),2和4不是一小步,因为2和4还可以同比例分解成更小的整数1和2(可以看出2和4的最大公约数为2),所以1和2才是一小步.x=x+1,y=y+2.
所以我们求出i到j的向量后只要求dx与dy的最大公约数就可以知道一小步是多少,然后最多走4步不越界即可,判断每一步是否合法.
AC代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
long long d[16][1<<16];
int g[10][10];//保存初始矩阵
int cnt;//共有cnt个0号点
int x[20],y[20];//x[i]和y[i]表示第i个0号点的x与y坐标
int gd[6][6];
int mp[20][20];//mp[i][j]=x表示(i,j)格是第x个0号点
int gcd(int a,int b)//最大公约数
{
if(b==0)return a;
return gcd(b,a%b);
}
bool has_way(int j,int i,int S1)//判断在状态S1下从j到i之间是否有路
{ //即j到i之间是否有1号点或没走过的0号点
int x1=x[i],y1=y[i],x2=x[j],y2=y[j];
int dx = x1-x2,dy=y1-y2;//从1到2的绝对移动距离
int d = gd[abs(dx)][abs(dy)];
dx=dx/d;//一小步的x位移量
dy=dy/d;//一小步的y位移量
for(int step=1;step<=4;step++)//最多走4步
{
int x3 =x2 + step*dx;
int y3 =y2 + step*dy;
if(x3==x1&&y3==y1)break;//从(x2,y2)点到达了(x1,y1)点
if(g[x3][y3]==0)//0号点
{
int k = mp[x3][y3];
if( !(S1&(1<<k)) )//S1不包含k,即k是为走过的0号点
return false;
}
else if(g[x3][y3]==1)//1号点不能走
{
return false;
}
}
return true;
}
long long dp(int i,int S)
{
if(d[i][S]>=0)return d[i][S];
long long &ans = d[i][S];
int S1 = S^(1<<i);
ans = 0;
for(int j=0;j<cnt;j++)
if(j!=i&& S1&(1<<j) && has_way(j,i,S1))
ans += dp(j,S1);
return ans;
}
int main()
{
for(int i=0;i<6;i++)
for(int j=i;j<6;j++)
gd[i][j]=gd[j][i]=gcd(i,j);
while(scanf("%d%d",&n,&m)==2)
{
cnt= 0;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
scanf("%d",&g[i][j]);
if(g[i][j]==0)
{
x[cnt]=i;
y[cnt++]=j;
mp[i][j]=cnt-1;//坐标->0号点编号 的映射
}
}
memset(d,-1,sizeof(d));//非法状态无法到达,直接初始为-1
for(int i=0;i<cnt;i++)
d[i][1<<i]=1;
long long sum=0;
int all=(1<<cnt)-1;
for(int i=0;i<cnt;i++)
sum += dp(i,all);
printf("%I64d\n",sum);
}
return 0;
}