题目大意
给出一个序列,现在的时间是从 1 1 1开始计时的,每次增加一分钟,对于一个固定的时间 t t t能任意选择序列的一个数 a i a_i ai,此时对答案的贡献为 ∣ t − a i ∣ |t-a_i| ∣t−ai∣,每个数只能且必须选一次,问最小的贡献和是多少。
解题思路
一开始以为是贪心,但是写的代码有点难调,后来就干脆放弃了,好像题解里面没有写贪心的。实际上对于数据来说,完全支持 O ( n 2 ) O(n^2) O(n2)的复杂度。而且数据范围比较小,一般来说这样的题目要么是思维模拟要么就是DP了。
首先对序列排序是肯定的,设 d [ i ] [ j ] d[i][j] d[i][j]为对于第 i i i个数在时间 j j j之前得到的最优解,对于本题来说是如何使得时间的分配不会重复。实际上对于当前的时间 t t t,如果选择了当前的数,那么对下一个数产生的影响是只能选择大于 t t t的时间,于是启发我们使用刷表法,那么状态转移方程为:
d [ i + 1 ] [ j + 1 ] = m i n ( d [ i + 1 ] [ j + 1 ] , d [ i ] [ j ] + a b s ( j − a [ i ] ) ) d[i+1][j+1]= min(d[i+1][j+1],d[i][j]+abs(j-a[i])) d[i+1][j+1]=min(d[i+1][j+1],d[i][j]+abs(j−a[i]))
上述状态转移方程给定是选择该时间,如果不选择该时间,怎么得到后面时间最优还是前面的选择更优?类似于 01 01 01背包,只需要不断将当前数得到的最优解随着时间增加而向后更新:
d [ i ] [ j + 1 ] = m i n ( d [ i ] [ j + 1 ] , d [ i ] [ j ] ) d[i][j+1]=min(d[i][j+1],d[i][j]) d[i][j+1]=min(d[i][j+1],d[i][j])
然后就是设想所有的数都是 200 200 200,那么显然时间最多可以选到 400 400 400,因此时间 j j j更新到 2 ∗ n 2*n 2∗n或者直接设置为 400 400 400。
初始化和边界
要将dp数组初始化为正无穷,而 d [ 1 ] [ 1 ] = 0 d[1][1]=0 d[1][1]=0,很像背包问题的边界设置。
答案
可以直接在DP过程更新答案到 d [ n + 1 ] [ 2 n ] d[n+1][2n] d[n+1][2n],也可以最后遍历所有的 d [ n + 1 ] [ j ] d[n+1][j] d[n+1][j]取最小值。
//
// Created by Happig on 2020/11/4
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define ENDL "\n"
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 2e5 + 10;
int a[205], d[205][405];
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t, n;
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + 1 + n);
memset(d, 0x3f, sizeof d);
d[1][1] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= 400; j++)
if (d[i][j] < inf) {
d[i + 1][j + 1] = min(d[i + 1][j + 1], d[i][j] + abs(a[i] - j)); //选择当前时间得到的最小值
d[i][j + 1] = min(d[i][j + 1], d[i][j]); //将取第i个物品的最优解向后传递
}
}
int ans = inf;
for (int j = 1; j <= 401; j++) {
ans = min(ans, d[n + 1][j]);
}
cout << ans << ENDL;
}
return 0;
}