[矩阵求逆+二分图匹配]BZOJ 3168 [Heoi2013]钙铁锌硒维生素

本文探讨了在保持线性无关性的前提下,通过矩阵匹配实现向量子典序最小解的问题。给出了详细的解题思路,包括利用矩阵求逆、匈牙利算法和贪心策略。介绍了具体实现过程及代码示例。

题目梗概

给出两个\(n*n\)的矩阵\(A,B\),将\(A,B\)中的向量进行匹配,使得\(A\)的任意向量被匹配的向量替换后,\(A\)仍线性无关,求字典序最小解,保证初始时\(A\)线性无关.

解题思路

因为\(A\)\(n\)维空间中的极大线性无关,所以\(A\)\(n\)维空间的一组基,\(B\)中的任意向量可以用\(A\)的组合表示.

然后有一个结论就是如果\(B\)中的向量\(i\)能替换\(A\)中的向量\(j\),那么用\(A\)表示\(i\)的时候\(j\)的系数不为\(0\).

感觉比较显然...于是设系数矩阵\(C\),有\(C*A=B\),\(C=B*A^{-1}\).

关于矩阵求逆:在把\(A\)消成单位矩阵的时候,对一个单位矩阵进行同样的初等变换.

然后\(C\)的转置显然就是邻接矩阵.

一些细节:字典序最小的求法就是先刷一遍匈牙利然后贪心的修正一遍,因为我们只关心\(C\)中元素是否为零,所以用任意的一个质数在矩阵求逆的时候进行取模就行.

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=305,tt=1e9+7,maxm=90005;
int n,f[maxn],ans[maxn];
bool vis[maxn];
int qsm(int w,int b){int num=1;while(b){if (b&1) num=(LL)num*w%tt;w=(LL)w*w%tt;b>>=1;}return num;}
int a[maxn][maxn],b[maxn][maxn],c[maxn][maxn];
void mul(int c[][maxn],int a[][maxn],int b[][maxn]){
    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++){
        c[i][j]=0;for (int k=1;k<=n;k++) (c[i][j]+=(LL)a[i][k]*b[k][j]%tt)%=tt;
    }
}
void get_INV(){
    for (int i=1;i<=n;i++){
        if (!a[i][i]) for (int j=i+1;j<=n;j++) if (a[j][i]) swap(a[i],a[j]),swap(c[i],c[j]);
        int w=qsm(a[i][i],tt-2);for (int j=1;j<=n;j++) a[i][j]=(LL)a[i][j]*w%tt,c[i][j]=(LL)c[i][j]*w%tt;
        for (int j=1;j<=n;j++) if (a[j][i]&&i!=j){
            w=a[j][i];for (int k=1;k<=n;k++) a[j][k]=(a[j][k]-(LL)a[i][k]*w%tt+tt)%tt,c[j][k]=(c[j][k]-(LL)c[i][k]*w%tt+tt)%tt;
        }
    }
}
bool find(int x){
    for (int i=1;i<=n;i++) if (a[i][x]&&!vis[i]){
        vis[i]=1;
        if (!f[i]||find(f[i])){
            f[i]=x;return 1;
        }
    } 
    return 0;
}
bool work(int x,int fa){
    for (int i=1;i<=n;i++) if (a[i][x]&&!vis[i]){
        vis[i]=1;
        if (f[i]==fa||(f[i]>fa&&work(f[i],fa))){
            f[i]=x;return 1;
        }
    }
    return 0;
}
int main(){
    freopen("exam.in","r",stdin);
    freopen("exam.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) scanf("%d",&a[i][j]);
    for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) scanf("%d",&b[i][j]);
    for (int i=1;i<=n;i++) c[i][i]=1;get_INV();mul(a,b,c);
    for (int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        if (!find(i)){printf("NIE\n");return 0;}
    }
    for (int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        work(i,i);
    }
    for (int i=1;i<=n;i++) ans[f[i]]=i;
    printf("TAK\n");for (int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
} 

转载于:https://www.cnblogs.com/CHNJZ/p/10443662.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值