bzoj 4004
据说是裸题...
据说这个东西叫实数线性基...
我说这个毒瘤题卡精度!!!
吐槽结束,进入正文:
本题的核心思想:贪心!按代价从小到大排序后插入线性基,如果能表示出来就不插入,但这里的插入是高斯消元的方法进行的。
然后我们讨论几个问题:
第一:不用考虑购买装备数最多这个问题!
这是在读题时第一个进入我脑子里的问题,但是仔细想想其实这个问题根本不存在!
证明:主要是感性理解一下:设原向量集合不变,接下来来了三个向量$a,b,c$,我们假设如果插入$a$能表示$b,c$,那么如果插入$b$,一定能表示$a,c$!
为什么?
我们想一下:利用$a$和原向量集合可以表示出$b$,那么如果移项就一定可以用$b$表示出$a$!
那么我们已经能用$b$表示出$a$了,我们自然可以表示出$c$!
因此并不会出现由于取了某一个向量导致取的向量总个数减少的现象!
第二:
怎么维护这个线性基?
常见的线性基都是在位运算的异或意义下的,所以容易维护,而这个线性基是实数意义下的,有些困难。
有两种方法:一种是模一个大质数变成模方程组高斯消元解(但是我不会)
另一种是直接高斯消元(但是卡精度)
我们介绍第二种:
对于每个新加入的向量,如果某一个位置上线性基为0,那么直接插入即可
但是,如果这一个位置上线性基不为0,那么我们就要消掉这个位置上的值(减成0)
消的方法和高斯消元一样,然后继续检查下一位即可
贴代码:
#include <cstdio> #include <cmath> #include <algorithm> using namespace std; double eps=0.00001; struct node { double a[505]; int val; friend bool operator < (node x,node y) { return x.val<y.val; } }p[505]; double b[505][505]; int n,m; int ans,cnt; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++)scanf("%lf",&p[i].a[j]); } for(int i=1;i<=n;i++)scanf("%d",&p[i].val); sort(p+1,p+n+1); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(fabs(p[i].a[j])<=eps)continue; if(fabs(b[j][j])<=eps) { for(int k=j;k<=m;k++)b[j][k]=p[i].a[k]; cnt++,ans+=p[i].val; } double now=p[i].a[j]; for(int k=j;k<=m;k++)p[i].a[k]-=b[j][k]*now/b[j][j]; } } printf("%d %d\n",cnt,ans); return 0; }