HDU4026

来源:互联网 发布:什么是物联网大数据 编辑:程序博客网 时间:2024/06/05 14:49

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 0
原创粉丝点击