1. 程式人生 > 其它 >【數學 線性基】[JLOI2015]裝備購買

【數學 線性基】[JLOI2015]裝備購買

傳送門:
https://www.acwing.com/problem/content/description/211/

分析

採取這樣的貪心策略:將物品看作是矩陣中的,按照花費升序排序,然後從 \(1-n\)​​ 掃描,當第 \(i\)​​ 個和前面加入的所有線性無關的時候,就將其花費計入答案,反之不計入。

這樣做為什麼是對的呢?採用歸納法來證明:

下證:前 \(n\)​​ 行採取上述策略能夠在保證選出的行構成的線性空間與前 \(n\)​ 行構成的線性空間相等的前提下花費最小。

  • \(1\) 行,我們肯定需要將第一行加入貢獻,滿足。
  • 假設前 \(k\) 行滿足上述貪心策略。
  • 下只需證明前 \(k+1\)
    行採取上述貪心策略是最優的。
    • 如果前 \(k\) 行構成的線性空間和前 \(k+1\) 行構成的線性空間相等(也就是第 \(k+1\)​​ 能被前 \(k\)​ 行選出的線性表出),那麼我們肯定不選取,滿足最優。
    • 如果不相等,也就是第 \(k+1\)​ 行與前 \(k\)​ 行線性無關。假設我們不選取第 \(k+1\)​ 行,那麼無論如何從前 \(k\)​ 行進行選取也不能使得選出的行構成的線性空間與前 \(k+1\)​​​ 行構成的線性空間相等,因此必須選。

實現

#include<bits/stdc++.h>
using namespace std;

#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dwn(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
#define all(x) (x).begin(), (x).end()

using ll = long long;

inline void read(int &x){
    int s=0; x=1;
    char ch=getchar();
    while(ch<'0' || ch>'9') {if(ch=='-')x=-1;ch=getchar();}
    while(ch>='0' && ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    x*=s;
}

const int N=550;
const double eps=1e-5;

int n, m;
int w[N][N], c[N];
int idx[N];

bool zero(double x){
	return abs(x)<eps;
}

bool zero(vector<double> a){
	for(auto i: a) if(!zero(i)) return false;
	return true;
}

void change(vector<double> &a, vector<double> &b){
	rep(i,0,m-1){
		if(zero(a[i]) && !zero(b[i])) return;
		if(!zero(a[i]) && zero(b[i])){
			swap(a, b);
			return;
		}
		if(!zero(a[i]) && !zero(b[i])){
			double rate=a[i]/b[i];
			rep(j,i,m-1) a[j]-=rate*b[j];
			return;
		}
	}
}

int main(){
	cin>>n>>m;
	rep(i,1,n) rep(j,1,m) read(w[i][j]);
	rep(i,1,n) read(c[i]);
	rep(i,1,n) idx[i]=i;
	sort(idx+1, idx+1+n, [](int x, int y){
		return c[x]<c[y];
	});
	
	vector<vector<double>> a;
	ll res=0;
	rep(i,1,n){
		int p=idx[i];
		vector<double> row;
		rep(j,1,m) row.pb(w[p][j]);
		
		for(auto &vec: a) change(row, vec);
		if(!zero(row)) a.pb(row), res+=c[p];
	}
	
	cout<<a.size()<<' '<<res<<endl;
	
	return 0;