[BZOJ]1059 矩阵游戏(ZJOI2007)
虽然说是一道水题,但小C觉得还是挺有意思的,所以在这里mark一下。
Description
Input
Output
输出文件应包含T行。对于每一组数据,如果该关卡有解,输出一行Yes;否则输出一行No。
Sample Input
2
2
0 0
0 1
3
0 0 1
0 1 0
1 0 0
Sample Output
No
Yes
HINT
对于100%的数据,N ≤ 200。
Solution
拿到这道题目肯定要思考,什么样的局面通过交换行列可以变成主对角线上都为黑格的情况?
由于交换是可逆的,我们倒过来思考,主对角线上都是黑格的情况通过交换行列可以变成什么局面。
这样就一目了然了。一开始是1~n的排列,每次交换行列相当于交换两个位置上的数字。
这样问题就转化成:是否能从每行选出恰好一个黑格,n行黑格代表的列数正好组成一个1~n的全排列。
正解是二分图匹配。全排列可以看作是行和列的匹配,思路很清晰。时间复杂度O(n^2)(匈牙利算法)/O(n^1.5)(网络流)。
但是小C这里要介绍的,是另一种做法。
想到矩阵和排列,我们会很自然地想起行列式。
我们思考一下行列式的计算公式:。
发现如果一个排列上的所有数都不为0,就一定会对答案有贡献!
所以我们就直接求这个矩阵的行列式的值就可以了???复杂度O(n^3)。
当然不行,这些排列对答案的贡献有正有负,矩阵的值全为0或1的话很容易凑出0。例如{{0,1,1},{0,1,1},{1,0,0}}。
那怎么办?
很简单啊,我们给矩阵里的黑格都随机一个权值就好了嘛。
什么?还是WA?
我们来看一看行列式等于0还有什么条件:
1.有一行或一列全为0的情况;
2.有两行或两列数值成比例的情况;
3.行向量之间或列向量之间有相关的情况;
4.逆矩阵不存在的情况:
5.行列式对应的矩阵的秩小于行列式的阶数的情况……
够了!似乎第二条看起来特别扎眼,于是我们给矩阵里的黑格都随机一个质数权值如何?
过了卧槽。
二分图匹配:
#include <cstdio> #include <cstring> #include <algorithm> #define MN 205 using namespace std; bool u[MN],mp[MN][MN]; int mat[2][MN]; int t,n; inline int read() { int n=0,f=1; char c=getchar(); while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();} while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();} return n*f; } bool dfs(int x) { if (u[x]) return false; u[x]=true; for (register int i=1;i<=n;++i) { if (!mp[x][i]||mat[0][x]==i) continue; if (!mat[1][i]||dfs(mat[1][i])) {mat[0][x]=i; mat[1][i]=x; return true;} } return false; } int main() { register int i,j; t=read(); while (t--) { n=read(); for (i=1;i<=n;++i) for (j=1;j<=n;++j) mp[i][j]=read(); memset(mat,0,sizeof(mat)); for (i=1;i<=n;++i) { memset(u,0,sizeof(u)); if (!dfs(i)) break; } if (i<=n) puts("No"); else puts("Yes"); } }
高斯消元求行列式:
#include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> #define mod 1000000007 #define MP 2000005 #define MN 205 using namespace std; int a[MN][MN],p[MP]; bool u[MP]; int t,n,pin; inline int read() { int n=0,f=1; char c=getchar(); while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();} while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();} return n*f; } int mi(int x,int y) { register int z=1; for (;y;x=1LL*x*x%mod,y>>=1) if (y&1) z=1LL*z*x%mod; return z; } void init() { register int i,j; for (i=2;i<MP;++i) { if (!u[i]) p[++pin]=i; for (j=1;i*p[j]<MP;++j) { u[i*p[j]]=true; if (i%p[j]==0) break; } } } int main() { register int i,j,k,x,mxi,lt; srand(9875321); init(); t=read(); while (t--) { n=read(); for (i=1;i<=n;++i) for (j=1;j<=n;++j) if (read()&1) a[i][j]=p[(rand()*rand()%pin+rand())%pin+1]; else a[i][j]=0; for (i=1;i<n;++i) { mxi=i; for (j=i+1;j<=n;++j) if (a[j][i]>a[mxi][i]) mxi=j; if (mxi!=i) swap(a[mxi],a[i]); if (!a[i][i]) break; for (j=i+1;j<=n;++j) for (lt=1LL*mi(a[i][i],mod-2)*a[j][i]%mod,k=i;k<=n;++k) a[j][k]=(a[j][k]-1LL*a[i][k]*lt%mod+mod)%mod; } if (i<n||!a[n][n]) puts("No"); else puts("Yes"); } }
Last Word
行列式还能这么用.jpg。
这题又让小C了解了一下行列式的一点点性质,好像还练习了一下乱搞技巧?
小C的n^3做法似乎成功拿到了bzoj那一题的垫底: