题解 - P4767弱化版 邮局

题目描述

高速公路旁边有\(n \space(n\leq300)\)个村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标\(a_i \space (a_i \leq 10^5)\)标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。

你要建立\(m \space (m \leq 30)\)个邮局,邮局将建在部分村庄中。你需要选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。求这个最小的距离总和。


做题方法


首先,作为数学班的数学大师们,应该都知道小学中的一道题:数轴上有\(n\)个已知点,则在数轴上距这些点之和最小的点必定是所有点顺序上的最中间的点(\(n\mod2 = 1\))或最中间两个点内部的任意点(\(n\mod2 = 1\))。

我们可以设一个状态\(f_{i,j}\)表示前\(i\)个村庄的范围内建立\(j\)个邮局的最小范围和。则答案显然就是\(f_{n,m}\)了。

对于一个\(f_{i,j}\),就必定会有能推出来这个\(f_{i,j}\)的前面的状态。我们再想,既然前\(i\)个村庄中有\(j\)个邮局,那么在前\(i\)个村庄内部,第\(j\)个村庄前面,就一定存在第\(j-1\)个邮局,而我们建设这个第\(j\)个村庄的状态的答案也受到上一个邮局的影响。但是,我们明显不知道上一个邮局在哪里的时候,\(f_{i,j}\)最小,所以我们需要枚举一遍\(k\)以枚举之前的状态,然后我们就可以看出来:

\[f_{i,j} = \min_{k=0}^{i-1}\{f_{k,j-1} + \rm dis(k+1,\space j)\} \]

其中,dis(l,r)表示\([l,r]\)的最小距离和,前面已经说明了求法,可以\(\mathcal O(n)\)计算。一共是\(\mathcal O(n^3m)\)


Code


//Luogu上原题30pts
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 305, maxm = 305;
const int inf = 0x3f3f3f3f;
int n, m, a[maxn];
int dp[maxn][maxm];
inline int dis(int l, int r){
	int mid = (l+r)>>1, ret = 0;
	for(int i = l; i <= r; ++i){
		ret += abs(a[i]-a[mid]);
	}
	return ret;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= n; ++i) {
		scanf("%d",&a[i]);
	}
	sort(a+1,a+1+n);
	memset(dp, inf, sizeof(dp));
	dp[0][0] = 0;
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j <= min(i,m); ++j){
			for(int k = 0; k < i; ++k){
				dp[i][j] = min(dp[i][j], dp[k][j-1] + dis(k+1,i));
			}
		}
	}
	cout << dp[n][m] << '\n';//cout较printf更快
	return 0;
}

等等!这道题不是\(\mathcal O(n^3m)\)吗!我记得oiClass上的题不是\(m\leq30\)\((n\leq300)\)的吗?这样,复杂度算上来是\(300^3\times30=810,000,000\)的吗?不是会TLE了吗?

我自裁。我在周五的时候打这篇题解的时候,大脑降级,算错复杂度了,算成\(\mathcal O(nm^3)\)了(悲)

我们可以看出,由于\(n\)很小,我们甚至可以直接预处理一遍\(\rm dis\)所计算的所有值,然后存在数组里。这样子就可以少掉在dp过程中的一个\(\mathcal O(n)\)的计算dis的过程。预处理dis很明显是\(\mathcal O(n^3)\)的,所以总复杂度为\(\mathcal O(n^3+n^2m)\)的。

//Luogu上原题40pts,拿到了所有的m<=300, n<=300的分,所以oiClass上大概是正解。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 305, maxm = 305;
const int inf = 0x3f3f3f3f;
int n, m, a[maxn];
int dp[maxn][maxm];
int d[maxn][maxn];
inline int dis(int l, int r){
	int mid = (l+r)>>1, ret = 0;
	for(int i = l; i <= r; ++i){
		ret += abs(a[i]-a[mid]);
	}
	return ret;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= n; ++i) {
		scanf("%d",&a[i]);
	}
	sort(a+1,a+1+n);
    for(int i = 1; i <= n; ++i){
        for(int j = i; j <= n; ++j){
            d[i][j] = dis(i,j);
        }
    }
	memset(dp, inf, sizeof(dp));
	dp[0][0] = 0;
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j <= min(i,m); ++j){
			for(int k = 0; k < i; ++k){
				dp[i][j] = min(dp[i][j], dp[k][j-1] + d[k+1][i]);
			}
		}
	}
	cout << dp[n][m] << '\n';
	return 0;
}

那么,现在你已经对DP的基本方法有了一定了解,就让我们来看一看下面这个简单的题目,来把我们刚刚学到的知识运用到实践中吧。

原题(IOI2000真题

加强版

posted @ 2020-09-24 13:23  zimindaada  阅读(182)  评论(0编辑  收藏  举报