ZOJ 3256 Tour in the Castle
现在给你一个N*M的矩阵,要求你从左上角走到左下角,且矩阵中的每个格子都要走仅1次,问你有多少种走法,输出对7777777求余之后的结果。
输入:包含最多19组实例。每个实例一行,为N和M (2 <= N <= 7, 1 <= M <=10^9).
输出:输出路线总数%7777777的结果。
分析:假设现在有一个4*4的矩阵如下图:
1,1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
假设现在在填(1,1)这个格子,所以初始轮廓线状态是0.如果填完了第一行,且此时的插头状态是1100,那么表示前两个格有插头,后两个格子无插头,1100能产生的后续插头状态是(每个下一行的格子都需要填,不能为空)0011,1111,1001(只有这3个,再无其他).矩阵的所有有效插头状态是那些二进制中只包含偶数个1的数(包括全0),比如1100,0000,1001,0011,1111等。假设一共有n个有效的插头状态,那么由于插头之间的相互转换状态可以得到一个递推关系:
令f[i]=ai,0*f[0]+ai,1*f[1]+ai,2*f[2]+…+ai,n-1*f[n-1](其中ai,0到ai,n-1 的值为0或1).此公式的意思的是,当前行的插头状态是第i个有效状态时,它出现的次数是前一行的插头状态为第x个有效状态时的次数的总和.其中由x可以退出状态i.
比如前一行1100和0000分别出现了5次和8次,但是其它有效状态只出现了0次,那么状态1111(可由1100和0000生成)在下一行出现的次数是5+8=13次.
题中矩阵从第1列开始,我们假设还存在第0列,且第0列的插头状态是1001状态,那么生成第1列的插头状态必定符合起点和终点只有1个出插头的要求.
合法状态:二进制表示中1的个数为偶数的状态是合法状态.
首先求出所有合法状态(不包含全0,因为只有一个圈,所以中途不可能出现从第i-1列到第i列每插头的情况).令f[i]=0,且f[100…001]=1.这是第0列给第1列的插头初值 ,由转换矩阵A= 可以求得第1列给第2列的插头初值为: * =
表示处理完第1列,在处理第2列之前的插头状态为有效状态1时的个数为f1,为有效状态2时的个数为f2,为有效状态3时的个数为f3,为有效状态4时的个数为f4(假设一共4个有效状态).
转换矩阵A= 表示的意思是:如果要生成第i个有效状态,那么需要ai1个第1号有效状态,需要…,需要ai4个4号有效状态.用A^(M-1)*(0列的初值)= 为处理完第M-1列,当前要处理最后一列第M列之前的插头状态,我们要最后一列处理完插头为全0,所以看那些有效状态可以生成全0的插头集合,如果能生成就加上这个插头的fi值.
上面的解法是对的,但是有个小BUG,因为由上一行生成下一行的插头状态有可能会连通了属于同一个连通分量的两个插头,然后直接在矩阵内形成了圈,这样是错的,所以需要采用最小表示法,把不同的连通分量标号不同的数.由于M<=7,所以最多只有3个连通分量,加上0表示没插头所以用4进制数表示状态.然后在[1,4^M-1]范围内枚举状态,看这个状态是否是一个合法的状态.首先要用最小表示法把这个状态重新表示出来.
一个合法的状态:其4进制表示中只有2个或0个1或2或3.(有可能没有3,但是由最小表示法生成的,只要有3就有2和1).且该状态的1,2,3插头不能交叉,即不能出现121或212 或2323这种情况.只要满足上面两点就是一个合法的状态.还有一点生成的插头状态首位要为0.因为列数为m列,但是插头状态有m+1位.
做了很久始终是错的,因为我上面的那种生成所有有效状态的方法有BUG,会生成多余的状态等等.
下面从初始态0和100..001来生成所有可能的有效状态,保存起来,然后找到有效状态i和有效状态j之间的生成关系,如果从i可以生成j那么A[i][j]=1,否则为0.另外还要找到从其他有效状态到0状态的关系,但是0状态不能生成任何其他状态(因为只能有1个圈).
本题中状态要用8进制表示,因为列为7时,有8个插头,会出现14412233这种情况(已测试,出现了),但它还是合法状态会生成合法的后续状态,如果用4进制数表示状态就出错.
AC代码:840ms
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int STATE=1010;
const int HASH = 30007;
const int MAXD=15;
const int MOD=7777777;
/**********下面是模板部分**************/
const int MAXN=200;
const int MAXM=200;
struct Matrix
{
int n,m;
long long a[MAXN][MAXM];
void clear()
{
n=m=0;
memset(a,0,sizeof(a));
}
Matrix operator +(const Matrix &b)const
{
Matrix tmp;
tmp.n=n;
tmp.m=m;
for(int i=0; i<n; ++i)
for(int j=0; j<m; j++)
tmp.a[i][j] = a[i][j]+b.a[i][j];
return tmp;
}
Matrix operator -(const Matrix &b)const
{
Matrix tmp;
tmp.n=n;
tmp.m=m;
for(int i=0; i<n; ++i)
for(int j=0; j<m; j++)
tmp.a[i][j] = a[i][j]-b.a[i][j];
return tmp;
}
Matrix operator *(const Matrix &b)const
{
Matrix tmp;
tmp.clear();
tmp.n=n;
tmp.m=b.m;
for(int i=0; i<n; ++i)
for(int j=0; j<b.m; j++)
for(int k=0; k<m; k++)
tmp.a[i][j] = (tmp.a[i][j]+a[i][k]*b.a[k][j])%MOD;
return tmp;
}
Matrix get(int x)//方阵幂运算,*this必须为方阵且x>=0且为整数,得到(*this)的x次幂方阵
{
Matrix E;
E.clear();
E.n=E.m=n;
for(int i=0; i<n; i++)
{
E.a[i][i]=1;
}
if(x==0)return E;
else if(x==1)return *this;
Matrix tmp = E,b=*this;//用的是迭代方式求矩阵幂,可以节约很多内存
while(x)
{
if(x&1)tmp = tmp*(b);
b = b*b;
x>>=1;
}
return tmp;
}
};
/**********上面是模板部分**************/
Matrix A;
int N,M;
int ex,ey;//最后一个好格的坐标
int cur;
int mp[MAXD][MAXD];
int code[MAXD];
long long sum;//记录最终的最大值
struct HASHMAP
{
int size,head[HASH],next[STATE];//next[i]=j表示第i个状态后面链接着第j个状态
int state[STATE];//state[i]=S表第i个状态是S
void init()
{
memset(head,-1,sizeof(head));
size=0;
}
int push(int st)
{
int h = st%HASH;
int i;
for(i=head[h]; i!=-1; i=next[i])
{
if(state[i]==st)
{
return i;
}
}
next[size]=head[h];
head[h]=size;
state[size]=st;
size++;
return size-1;
}
} hm[2],hm_1;
void decode(int *code,int st)//st->code
{
for(int i=M; i>=0; i--)
{
code[i]=st&7;//st用8进制表示因为当有7列的时候,就有8个插头,那么会出现14412233这种情况
//且这种情况还是合法的,能生成后继合法状态,所以用4进制不行
st>>=3;
}
}
int encode(int *code)//code->st
{
int ch[MAXD];
memset(ch,-1,sizeof(ch));
ch[0]=0;
int cnt=1;
int st=0;
for(int i=0; i<=M; i++)
{
if( ch[code[i]]==-1 ) ch[code[i]]= cnt++;
code[i]=ch[code[i]];
st<<=3;
st|=code[i];
}
return st;
}
void shift(int *code)//处理完了一行的最后一列,将code整体右移一位,首位添0
{
for(int i=M; i>=1; i--)
code[i]=code[i-1];
code[0]=0;
}
void dpblank(int i,int j)//好格
{
for(int k=0; k<hm[cur].size; k++)
{
int st=hm[cur].state[k];
int code[MAXD];
decode(code,st);
int left=code[j-1] ,up=code[j];
if(left>0&&up>0)//都有插头
{
if(left!=up)//只能合并两个连通分量
{
code[j-1]=code[j]=0;
for(int l=0; l<=M; l++)
if(code[l]==up)
code[l]=left;
if(j==M)shift(code);//j为当前行最后一列,需要右移一位
hm[1-cur].push( encode(code));
}
else if(i==ex&&j==ey)//最后一个好格
{
code[j-1]=code[j]=0;
if(j==M)shift(code);//j为当前行最后一列,需要右移一位
hm[1-cur].push( encode(code));
}
}
else if(left>0||up>0)//其中一个有插头另一个没有
{
if(mp[i][j+1]==1)//(i,j)右边是个好格
{
code[j-1]=0;
code[j]=left+up;
if(j==M)shift(code);//j为当前行最后一列,需要右移一位
hm[1-cur].push( encode(code));
}
if(mp[i+1][j]==1)//(i,j)下面是个好格
{
code[j-1]=left+up;
code[j]=0;
if(j==M)shift(code);//j为当前行最后一列,需要右移一位
hm[1-cur].push( encode(code));
}
}
else//两个都没插头
{
if(mp[i][j+1]==1&&mp[i+1][j]==1)//(i,j)格的右边和下边是可行格,才可以新生成一个连通分量
{
//这里不会出现j是最后一列的情况
int max_c=1;//表示当前扫描到的最大插头编号
for(int l=0; l<=M; l++)
if(max_c<code[l])
max_c = code[l];
code[j-1]=code[j]=max_c+1;//max_c可能为4
hm[1-cur].push( encode(code));
}
}
}
}
void init()
{
ex=2;
ey=M;//标记最后一个可行格
//假设有个2行M列的矩阵,且都为可行格,矩阵外围都是坏格
memset(mp,0,sizeof(mp));
for(int i=1; i<=2; i++) //初始化只有两行的矩阵
for(int j=1; j<=M; j++)
mp[i][j]=1;
A.clear();//A.a[i][j]=1表示从第i个有效状态可以生成第j个有效状态
hm_1.init();
hm_1.push(0);//有效状态0入表,且从0不可能生成任何其他有效状态,因为图中只能有一个圈
memset(code,0,sizeof(code));
code[1]=code[M]=1;
hm_1.push(encode(code));//有效状态10...001入表,这个状态是我们假想的第0列的插头状态
for(int i=1; i<hm_1.size; i++)//由hm_1中的初始的有效状态100..1生成所有可能的后续状态
{
hm[cur].init();
hm[cur].push(hm_1.state[i]);
for(int j=1; j<=M; j++) //走完这M格,生成下一行的插头状态
{
hm[1-cur].init();
dpblank(1,j);//假设当前处理的是第1行,第2行也全是由好格组成
cur=1-cur;
}
for(int k=0; k<hm[cur].size; k++) //取出生成的状态
{
int st = hm[cur].state[k];
int l = hm_1.push(st);//生成的状态在hm_1.state中的编号
A.a[i][l]=1;//由第i个有效状态可以生成第l个有效状态
}
}
for(int i=1; i<hm_1.size; i++)//这里是看看哪些有效状态可以生成第0个有效状态,即全0
{
hm[cur].init();
hm[cur].push(hm_1.state[i]);
for(int j=1; j<=M; j++)//生成下一行的插头状态
{
hm[1-cur].init();
dpblank(2,j);//最后一个可行格子是ex=2,ey=M
cur=1-cur;
}
if(hm[cur].size>0)//由第i个有效状态可以到有效状态0
A.a[i][0]=1;
}
A.n=A.m=hm_1.size;
}
void solve()
{
sum=0;
A=A.get(N);//有效状态100..001是我们假想在第0列到第1列的插头状态,我们要求的是从0列(插头为100..001)到第N列的插头(为0000.00)的总路线条数
if(A.a[1][0]==0)
printf("Impossible\n");
else
printf("%lld\n",A.a[1][0]%MOD);
}
int main()
{
//freopen("out2.txt","w",stdout);
while(scanf("%d%d",&M,&N)==2&&M&&N)//m为更小的值,n为更大的值.
{
init();
solve();
}
return 0;
}