HDU4026

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;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值