矩阵乘法

zoj 3497 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3497

 题目意思是给定一个有向图(最多25个节点,每个节点的出度最多为4),给定起点和终点,然后从起点开始走,走到终点就停止,否则一直往下走,问能不能P步到达终点。也就是说从起点出发,走一条长度为P的路径,路径中间点不能经过终点(但可以反复经过其他点)。如果从起点出发P步后,不能到达终点,就是False,如果可以到达终点也可以到其他别的点,就是Maybe,如果P步后只能到达终点(到别的点没有长度为P的路径),则是Yes

         显然的矩阵乘法。图的临接矩阵A的 p次方Ap中为1的元素(i,j)表示节点i到节点j有一条长度为p的路径(经历的节点可能重复)。要理解矩阵的含义,两个矩阵相乘如果(x,y)元素为1,而(y,z)元素为1,则结果(x,z)元素为1,这表明通过yxz连起来了。而题目要求经过终点就不能走了,所以在做矩阵乘法时,需要把(x,n-1) (n-1,y)这样决定的(x,y)去掉。(n-1表示终点)。做乘法时,中间点小心一点就好了。矩阵乘法和floyd在本质上是一样的……

         矩阵的P次方运用的是经典的log(P)的算法。最后看一下结果矩阵的首行(0行)里面有几个1,以及(0,n-1)是不是1,来决定结果。

         一般矩阵的乘法要n3的复杂度。这个矩阵规模很小,n只有25,而且矩阵元素是0或者1,所以我们可以把每行用一个整数表示,每列也用一个整数表示,元素值按bit压缩至int里。实际上就是用两个数组,一个表示行向量r,一个表示列向量c,然后在做乘法时可以用位运算,r[i]&c[j]来决定结果矩阵(i,j)位置是0还是1,当然结果矩阵也要同时保存行向量和列向量,另外还是要去掉中间点为终点的转换。这样复杂度就只有n2了。

                                                                  ------------------------------转载http://blog.sina.com.cn/s/blog_5123df350100rff6.html

#include<iostream>
#include<cstring>
#include <cstdio>
#include<string>
#include<queue>
#include<vector>
#include<map>
#include <set>
#include<ctime>
#include<cmath>
#include <cstdlib>
#include<algorithm>
#include <iomanip>
#include <bitset>
using namespace std;
#define LL long long

#define MAX 1<<25
#define nMAX 50
#define MOD 1000000007
#define P_MAX 100000

struct node{
int x, y;
} ar[5];

int G[nMAX][nMAX],ans[nMAX][nMAX],f[nMAX][nMAX];
int T,n,m;
void martix(int mpp1[][nMAX],int mpp2[][nMAX]){
int temp[nMAX][nMAX]; memset(temp,0,sizeof(temp));
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
for(int k=1;k<n;k++) if(mpp1[i][k]&&mpp2[k][j]) {
temp[i][j]=true;
break;
}
}
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) mpp1[i][j]=temp[i][j];
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d\n",&n,&m);
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++){
scanf("((%d,%d),(%d,%d),(%d,%d),(%d,%d)) ",
&ar[1].x,&ar[1].y,
&ar[2].x,&ar[2].y,
&ar[3].x,&ar[3].y,
&ar[4].x,&ar[4].y);
f[(i-1)*m+j][m*(ar[1].x-1)+ar[1].y]=true;
f[(i-1)*m+j][m*(ar[2].x-1)+ar[2].y]=true;
f[(i-1)*m+j][m*(ar[3].x-1)+ar[3].y]=true;
f[(i-1)*m+j][m*(ar[4].x-1)+ar[4].y]=true;
}
scanf("\n");
}
n=n*m;
for(int i=1;i<=n;i++) f[n][i]=0;
int Q,P,PP;
scanf("%d",&Q);
while(Q--){
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) G[i][j]=f[i][j];
memset(ans,0,sizeof(ans));
scanf("%d",&P); PP=P;
bool flag=true;
for(;P;P>>=1){
if(P&1){
if(flag) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ans[i][j]=G[i][j];
else martix(ans,G);
flag=false;
}
martix(G,G);
}

if(n==1&&PP==0) printf("True\n") ;
else if(!ans[1][n]) printf("False\n");
else {
flag=true;
for(int i=1;i<n;i++){
if(ans[1][i]) {
flag=false;
break;
}
}
if(flag) printf("True\n");
else printf("Maybe\n");
}
}
printf("\n");
}
}



 

背景略过。以奇怪的输入格式给你一个有向图。如果所有到终点的线路长度都是p,输出True;如果存在这样的线路,输出Maybe;否则,输出False。

发现所给的图只有25个节点,而线路长度很大。那么可以用矩阵乘法来求从i到j,长度为k的路径是否存在。其实floyd算法求传递闭包就是把k=0~n-1的矩阵或起来。然后就可以判断是不是False,要区分True和Maybe则还要麻烦一点,可以考虑特判True。

我懒得写矩阵乘法,就水了一下。方法很简单,用2^n记录第k步哪些节点是可达的,那么这个状态必定会循环,于是找出循环节就好了。现场似乎有人也有类似的方法过了。关于复杂度,我只能确定有O(2^n)这个上界,但似乎实际数据不可能达到这么大,事实上所有数据都在1000步以内进入了循环节。

                                                                                                        ------转载 http://blog.watashi.ws/1944/the-8th-zjpcpc/

#include<iostream>
#include<cstring>
#include <cstdio>
#include<string>
#include<queue>
#include<vector>
#include<map>
#include <set>
#include<ctime>
#include<cmath>
#include <cstdlib>
#include<algorithm>
#include <iomanip>
#include <bitset>
using namespace std;
#define LL long long

#define MAX 1<<25
#define nMAX 50
#define MOD 1000000007
#define P_MAX 100000

struct node{
int x, y;
} ar[5];

int G[nMAX][5];
vector<int> v;
map<int,int> mpp;
int T,n,m,mod;
int L,R;
void solve(){
int a=1;
v.clear(); mpp.clear();
v.push_back(1); mpp[1]=1;
for(;;) {
int b=0;
for(int i=0;i<n-1;i++) if(a&(1<<i)){
for(int j=1;j<=4;j++){
b|=(1<<(G[i+1][j])-1);
}
}
a=b;
if(!mpp[a]) {
v.push_back(a);
mpp[a]=v.size();
}else {
L=mpp[a]-1;
R=v.size()-1;
break;
}
}
}
void print(int x){
if(! ( (1<<(n-1))&x ) ) {
printf("False\n");
return ;
}
for(int i=0;i<n-1;i++){
if((1<<i)&x) {
printf("Maybe\n");
return ;
}
}
printf("True\n");
}

int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d\n",&n,&m);
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++){
scanf("((%d,%d),(%d,%d),(%d,%d),(%d,%d)) ",
&ar[1].x,&ar[1].y,
&ar[2].x,&ar[2].y,
&ar[3].x,&ar[3].y,
&ar[4].x,&ar[4].y);
G[(i-1)*m+j][1]=m*(ar[1].x-1)+ar[1].y;
G[(i-1)*m+j][2]=m*(ar[2].x-1)+ar[2].y;
G[(i-1)*m+j][3]=m*(ar[3].x-1)+ar[3].y;
G[(i-1)*m+j][4]=m*(ar[4].x-1)+ar[4].y;
}
scanf("\n");
}
n=n*m;
solve();
int Q,P;
scanf("%d",&Q);
while(Q--){
scanf("%d",&P);
if(P<=R) print(v[P]);
else{
int x=(P-R)%(R-L+1)+L-1;
if((P-R)%(R-L+1)==0) x=R;
print(v[x]);
}
}
printf("\n");
}
}

 

posted @ 2012-04-01 15:31  HaoHua_Lee  阅读(321)  评论(0编辑  收藏  举报