题目

这道题的题意理解很重要,直接写原题了。

小林把人体需要的营养分成了\(n\)种,他准备了2套厨师机器人,一套厨师机器人有\(n\)个,每个厨师机器人只会做一道菜,这道菜一斤能提供第\(i\)种营养\(x_i\)微克。想要吃这道菜的时候,只要输入一个数,就能吃到对应数量的这道菜了。为防止摄入过量对身体造成的伤害,每个机器人还有防过量摄入药,只要输入一个数,就能生成一定剂量的药,吃了这些药,就能减少相当于食用对应数目的这道菜提供的营养。

小林之所以准备2套厨师机器人,是因为某一个厨师机器人可能坏掉,要是影响了银河队选手的身体,就不好了。因此,第2套厨师机器人被用来做第1套的备用。小林需要为每一个第1套厨师机器人选一个第2套厨师机器人作备份,使得当这个机器人坏掉时,用备份顶替,整套厨师机器人仍然能搭配出任何营养需求,而且,每个第2套厨师机器人只能当一个第1套厨师机器人的备份。求一种备份方案,使得字典序最小(按所备份的机器人编号从小到大输出)。

分析

首先,题中输入的“数”可以是小数,所以可以任意搭配。那“搭配出任何营养需求”是什么意思呢?也就是说,我可以通过改变每种菜的数量来获取任意组合的营养,即:

\[\begin{aligned}
y_i=\sum _ {j=1}^n x_j *A _{ji} \\\
\end{aligned}
\]

我们把他们都写成矩阵的形式:

\[\begin{aligned}
x*A=y
\end{aligned}
\]

所以判断一个矩阵能否组合出任意营养搭配,就是判断一个矩阵是否能通过消元变成对角阵。这个转化十分关键,其实就是看懂题意。

这是一个一一对应的问题,每个备用机器人只能换一个第一套机器人,然后就想到,这和二分图的匹配模型很相似。以后见到两种物品,一一对应在一个集合中选择给另一个集合匹配(例如gdkoi2017的演员那题)类似的题目,二分图应该是一种可行的解法。

现在的问题是,我们要求出每一个\(B\_j\)有那些\(A\_i\)可以替换。这里用到了一个很显然的结论。

\(A\)可以通过消元变成对角阵。若存在行向量\(\lambda\)使得\(\lambda\*A=b\),那么如果\(\lambda \_ i\ne 0\),那么把行\(A\_i\)替换为\(b\)后,新的矩阵也能消元得出对角阵。这是因为,一行不能被替换,当且仅当替换后这一行会被消成全0,即替换后这一行可以用别的行线性表示(每行乘以一个不为0的系数,行相加),于是就无法消元得出满对角阵。所以我们只能选择\(\lambda\)不为0的行替换。

由于\(\lambda\)有\(n\)个,不如我们把它们全部一起求出来。矩阵\(C\)表示所有的\(\lambda\),即\(C\_j\)表示\(B\_j\)的替换方案:

\[C*A=B \\
C*A*{A^{-1}}=B*{A^{-1}} \\
C=B*A^{-1} \\
\]

这样我们就求出了所有的\(\lambda\),即求出了二分图的邻接矩阵,\(C_{ij}\ne 0\)表示左边的\(j\)和右边的\(i\)有连边。这样就可以求二分图匹配了。现在的问题在于,如何求一个矩阵的逆矩阵。

例如原矩阵为:

\[\begin{bmatrix}
1 & 2 \\
3 & 4
\end{bmatrix}
\]

我们给出增广炬阵,并把左边消元成单位阵:

\[\left[
\begin{array}{cc|cc}
1 & 2 & 1 & 0 \\
3 & 4 & 0 & 1 \\
\end{array}
\right] \\
\left[
\begin{array}{cc|cc}
1 & 2 & 1 & 0 \\
0 & -2 & -3 & 1 \\
\end{array}
\right] \\
\left[
\begin{array}{cc|cc}
1 & 0 & 1 & 1 \\
0 & -2 & -3 & 1 \\
\end{array}
\right] \\
\left[
\begin{array}{cc|cc}
1 & 0 & -2 & 1 \\
0 & 1 & \frac{3}{2} & -\frac{1}{2} \\
\end{array}
\right] \\
\]

这时,右边得到的矩阵就是原矩阵的逆矩阵。所以矩阵求逆非常简单,只要对左边进行消元,变成对角阵即可。如果一个矩阵不可逆,那么左边无法形成对角阵。

最后一个问题,如何解决字典序最小。通过匈牙利算法的过程可以看出,我们只能保证得到的最后一个匹配的字典序最小,前面的无法保证。因此,我们可以通过一种调整来在匹配数量不变的情况下从前往后保证最小字典序。从小到大,对于左边的一个点,先把原来的匹配边记录下来并断开,再从小到大扫右边的点,跟之前一样找增广路。由于之前断开了匹配边,匹配数量减一;找到增广路后,匹配数量加一,故答案不变。只要找到了增广路,我们就把当前的左边点和右边点删掉(标记),把它们锁定,这样以后不会再更改到它们。这样既可求出最大匹配的最小字典序。其实最小字典序就是一个贪心的东西。

代码

用double直接消元比他们的大数取模快几倍,不过大数取模也是很好的方法。通过给定一个永远不会超过的大质数,可以方便地求乘法逆元,解决除法问题,不过每次多一个log。

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<cmath>
using namespace std;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxn=305;
const double eps=1e-9;
int b[maxn][maxn],match[maxn];
double a[maxn][maxn],c[maxn][maxn];
double h[maxn][maxn<<1];
int n;
bool alr[maxn];
int gcd(int x,int y) {
if (x<y) swap(x,y);
return y?gcd(y,x%y):x;
}
bool elm() {
for (int i=1;i<n;++i) {
if (!h[i][i]) {
for (int j=i+1;j<=n;++j) if (fabs(h[j][i])>eps) {
for (int k=1;k<=n+n;++k) swap(h[i][k],h[j][k]);
break;
}
}
for (int j=i+1;j<=n;++j) if (h[j][i]) {
double tmp=h[j][i]/h[i][i];
for (int k=1;k<=n+n;++k) h[j][k]-=h[i][k]*tmp,h[j][k]=(fabs(h[j][k])<eps?0:h[j][k]);
}
}
for (int i=n;i>1;--i) {
if (!h[i][i]) {
for (int j=i-1;j>0;--j) if (fabs(h[j][i])<eps) {
for (int k=1;k<=n+n;++k) swap(h[i][k],h[j][k]);
break;
}
}
for (int j=i-1;j>0;--j) if (h[j][i]) {
double tmp=h[j][i]/h[i][i];
for (int k=1;k<=n+n;++k) h[j][k]-=h[i][k]*tmp,h[j][k]=(fabs(h[j][k])<eps?0:h[j][k]);
}
}
for (int i=1;i<=n;++i) if (fabs(h[i][i])<eps) return false;
return true;
}
bool abx[maxn],aby[maxn];
bool dfs(int x) {
for (int i=1;i<=n;++i) if (fabs(c[i][x])>eps && !alr[i] && !aby[i]) {
alr[i]=true;
if (!match[i] || dfs(match[i])) {
match[i]=x;
return true;
}
}
return false;
}
void change(int x) {
int k;
for (int i=1;i<=n;++i) if (match[i]==x) {
k=i;
break;
}
match[k]=0;
for (int i=1;i<=n;++i) if (fabs(c[i][x])>eps && !alr[i] && !aby[i]) {
alr[i]=true;
if (!match[i] || (!abx[match[i]] && dfs(match[i]))) {
match[i]=x;
abx[x]=true;
aby[i]=true;
return;
}
}
abx[x]=aby[k]=true;
match[k]=x;
}
int Ans[maxn];
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("my.out","w",stdout);
#endif
n=read();
for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) h[i][j]=read();
for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) b[i][j]=read();
for (int i=1;i<=n;++i) h[i][i+n]=1;
bool flag=elm();
if (!flag) {
puts("NIE");
return 0;
}
for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) a[i][j]=(double)h[i][j+n]/h[i][i];
for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) for (int k=1;k<=n;++k) c[i][j]+=b[i][k]*a[k][j];
int ans=0;
for (int i=1;i<=n;++i) memset(alr,0,sizeof alr),ans+=dfs(i);
if (ans!=n) {
puts("NIE");
return 0;
} else puts("TAK");
for (int i=1;i<=n;++i) {
memset(alr,0,sizeof alr);
change(i);
}
for (int i=1;i<=n;++i) Ans[match[i]]=i;
for (int i=1;i<=n;++i) printf("%d\n",Ans[i]);
}

bzoj3168-钙铁锌硒维生素的更多相关文章

  1. bzoj3168 钙铁锌硒维生素 (矩阵求逆+二分图最小字典序匹配)

    设第一套为A,第二套为B 先对于每个B[i]判断他能否替代A[j],即B[i]与其他的A线性无关 设$B[i]=\sum\limits_{k}{c[k]*A[k]}$,那么只要看c[j]是否等于零即可 ...

  2. 【BZOJ3168】[Heoi2013]钙铁锌硒维生素 高斯消元求矩阵的逆+匈牙利算法

    [BZOJ3168][Heoi2013]钙铁锌硒维生素 Description 银河队选手名单出来了!小林,作为特聘的营养师,将负责银河队选手参加宇宙比赛的饮食.众所周知,前往宇宙的某个星球,通常要花 ...

  3. BZOJ 3168: [Heoi2013]钙铁锌硒维生素 [线性基 Hungary 矩阵求逆]

    3168: [Heoi2013]钙铁锌硒维生素 题意:给一个线性无关组A,再给一个B,要为A中每个向量在B中选一个可以代替的向量,替换后仍然线性无关.判断可行和求字典序最小的解 PoPoQQQ orz ...

  4. 洛谷 P4100 [HEOI2013]钙铁锌硒维生素 解题报告

    P4100 [HEOI2013]钙铁锌硒维生素 题目描述 银河队选手名单出来了!小林,作为特聘的营养师,将负责银河队选手参加 宇宙比赛的饮食. 众所周知,前往宇宙的某个星球,通常要花费好长好长的时间, ...

  5. BZOJ3168: [Heoi2013]钙铁锌硒维生素

    设$A^TC=B^T$,这样$C_{ij}$表示$B_j$的线性表出需要$A_i$,那么$B_j$可以替换$A_i$,根据$C=(A^T)^{-1}B^T$求出$C$.要求字典序最小完美匹配,先求任意 ...

  6. BZOJ3168. [HEOI2013]钙铁锌硒维生素(线性代数+二分图匹配)

    题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=3168 题解 首先,我们需要求出对于任意的 \(i, j(1 \leq i, j \leq ...

  7. [HEOI 2013 day2] 钙铁锌硒维生素 (线性代数,二分图匹配)

    题目大意 给定两个n阶方阵,方阵B的行i能匹配方阵A的行j当且仅当在第一个方阵中用行向量i替换行向量j后,第一个方阵满秩,显然这是个二分图匹配问题,问是否存在完美匹配,如果存在,还要输出字典序最小的方 ...

  8. 【BZOJ】3168: [Heoi2013]钙铁锌硒维生素

    题解 Ca Fe Zn Se 显然我们既然初始矩阵就能通过线性变换变成单位矩阵,则该矩阵一定有逆 没有逆输出NIE 而且因为这些向量两两正交,则表示一个向量的时候表示方法唯一 那么我们求一个逆可以求出 ...

  9. BZOJ 3168 Heoi2013 钙铁锌硒维生素 矩阵求逆+匈牙利算法

    题目大意:给定一个n∗n的满秩矩阵A和一个n∗n的矩阵B.求一个字典序最小的1...n的排列a满足将随意一个Ai换成Bai后矩阵A仍然满秩 我们考虑建立一个二分图.假设Ai能换成Bj.就在i−> ...

  10. BZOJ 3168 [Heoi2013]钙铁锌硒维生素 ——矩阵乘法 矩阵求逆

    考虑向量ai能否换成向量bj 首先ai都是线性无关的,然后可以a线性表出bj c1*a1+c2*a2+...=bj 然后移项,得 c1/ci*a1+...-1/ci*bj+...=ai 所以当ci不为 ...

随机推荐

  1. typescript入门,可以一起探讨提点意见互相学习。

    typescript是js的一个超集,TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以不加改变的在TypeScript下工作.TypeScript是为大 ...

  2. 【blockly教程】第三章Blockly顺序程序设计

    3.1 什么是Blockly语言  2012年6月,Google发布了完全可视化的编程语言Google Blockly,整个界面清晰明了, 你可以如同在玩拼图一样用一块块图形对象构建出应用程序.每个图 ...

  3. NoSQL入门第五天——Java连接与整合操作

    一.测试联通 1.新建个web工程 2.导入jar:当然实际使用的时候肯定是通过maven来构建(如果有机会,可以尝试学习gradle进行构建) 3.建个测试类:好久没开eclipse了,希望后面可以 ...

  4. Java基础—IO小结(二)缓冲流与其它流的使用

    一.缓冲流的使用 每个字节流都有对应的缓冲流: BufferedInputStream / BufferedOutputStream 构造器: 方法摘要与对应节点流类似 使用缓冲流实现文件复制:实际中 ...

  5. asp.net微信公众平台本地调试设置

    1.首先要开启内网穿透功能,我这边使用自己搭建的ngrok内网穿透服务(具体如何搭建ngrok内网穿透服务,另开一篇说) 2.开启内网穿透后,即可实现互联网访问 www.tbkmama.cn 指向 1 ...

  6. Ubuntu adb 报错:no permissions (user in plugdev group; are your udev rules wrong?);

    Ubuntu 下 adb 报错: caoxinyu@caoxinyu-ThinkPad-T470p:~/Android/Sdk/platform-tools$ ./adb devices List o ...

  7. CentOS 7.2使用源码包编译安装MySQL 5.7.22及一些操作

    CentOS 7.2使用源码包编译安装MySQL 5.7.22及一些操作 2018年07月05日 00:28:38 String峰峰 阅读数:2614   使用yum安装的MySQL一般版本比较旧,但 ...

  8. Unbuntu安装RVM

    apt-get install curl #安装rvm curl -L https://get.rvm.io | bash #执行启动 source /home/mafei/.rvm/scripts/ ...

  9. 聊聊WS-Federation(test)

    本文来自网易云社区 单点登录(Single Sign On),简称为 SSO,目前已经被大家所熟知.简单的说, 就是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统. 举例: 我们 ...

  10. NPOI List数据源 导出excel

    List数据源生成HSSFWorkbook通用方法: public class WorkBook { public static HSSFWorkbook BuildSwitchData<T&g ...