【ybt金牌导航8-2-5】【luogu P3265】【bzoj 4004】装备购买(贪心)(实数线性基)(高斯消元)
装备购买
题目链接:ybt金牌导航8-2-5 / luogu P3265 / bzoj 4004
题目大意
给你 n 个物品,每个物品有价格,和它的特征向量。
然后如果有一个东西可以通过某几个你已经买了的物品向量每一位乘各自各自的一个实数相加得到,那你就不可以买这个东西。(一个物品可以选实数,然后每一位都要乘这个)
然后问你最多能买多少个东西,在买最多东西的前提下最少要多少钱。
思路
首先我们可以通过各种各样的观察性质:
如果一些物品能凑出一个物品,那这一些物品中的任何一个都能被这些物品中剩下的和这个凑出。
(那物品数好像怎么搞都差不多)
那我们不难想到贪心,从价格小的开始搞。
那问题就变成每次判断能不能放了。
然后你会发现它这个凑的过程好像高斯消元的式子,然后你会发现你顶对是 \(n\) 个,因为你互补抵消的可以高斯消元中变成每个确定了一位向量的“值”。
然后你就考虑一个很神奇的东西叫做实数线性基(其中用高斯消元辅助实现)。
其实就是类似普通的线性基,我们每次不断的找位置,找到自己这一位非 \(0\)(注意精度问题,\(1e-6\) 是不行的,\(1e-5\) 就可以了,十分玄学),而且没有别的人占了的位置占据。
如果找到的位置别人占据了,那原来是异或,这里就是用高斯消元把这一位消掉。
然后这么搞就可以了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node {
double f[505];
int c;
}a[505];
int n, m, p[505];
int num, ans;
double eps = 1e-5;
bool cmp(node x, node y) {
return x.c < y.c;
}
double Abs(double x) {
return (x < 0) ? -x : x;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) scanf("%lf", &a[i].f[j]);
for (int i = 1; i <= n; i++) scanf("%d", &a[i].c);
sort(a + 1, a + n + 1, cmp);//贪心
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (Abs(a[i].f[j]) <= eps) continue;//线性基
if (!p[j]) {
p[j] = i; num++; ans += a[i].c;
break;
}
double tmp = a[i].f[j] / a[p[j]].f[j];//高消来弄
for (int k = j; k <= m; k++) {
a[i].f[k] -= tmp * a[p[j]].f[k];
}
}
}
printf("%d %d", num, ans);
return 0;
}