题目

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

小林把人体需要的营养分成了\(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. Java设计模式(12)——结构型模式之门面模式(Facade)

    一.概述 概念 简要示意图(没有一个统一的UML图) 角色 门面角色:门面模式核心,它被客户端调用,并且熟悉子系统   子系统角色:子系统,子系统并不知道门面的存在,门面对它来说只不过是另外一个客户端 ...

  2. Java基础—ArrayList源码浅析

    注:以下源码均为JDK8的源码 一. 核心属性 基本属性如下: 核心的属性其实是红框中的两个: //从注释也容易看出,一个是集合元素,一个是集合长度(注意是逻辑长度,即元素的个数,而非数组长度) 其中 ...

  3. BZOJ1452_Count_KEY

    题目传送门 二维树状数组,对于每个颜色开一个树状数组,用容斥求解. code: #include <cstdio> using namespace std; int read() { ') ...

  4. 成都Uber优步司机奖励政策(2月17日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  5. 天津市人民优步Uber司机奖励政策(9月14日~9月20日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  6. VINS(二)Feature Detection and Tracking

    系统入口是feature_tracker_node.cpp文件中的main函数 1. 首先创建feature_tracker节点,从配置文件中读取信息(parameters.cpp),包括: ROS中 ...

  7. SpringBoot-03:SpringBoot+Idea热部署

      ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 所谓热部署,就是在项目启动中,修改class类中做的修改操作,无需重新启动项目,就可以变更,在网页展示中有 ...

  8. 2019年猪年海报PSD模板-第七部分

    14套精美猪年海报,免费猪年海报,下载地址:百度网盘,https://pan.baidu.com/s/1pE3X9AYirog1W8FSxbMiAQ              

  9. 围绕DOM元素节点的增删改查

    HTML 文档中的所有内容都是节点: 整个文档是一个文档节点 document 每个 HTML 元素是元素节点 element HTML 元素内的文本是文本节点 每个 HTML 属性是属性节点 注释是 ...

  10. 「国庆训练」ArcSoft's Office Rearrangement(HDU-5933)

    题目与分析 题解见https://blog.csdn.net/cmershen/article/details/53200922. 训练赛场上我们写出来了--在4小时50分钟的时候...激情补题啊.. ...