题意:
找到两个序列p,q,这两个序列是1-n中每两个数之间交换所得的序列(每一个数都要被交换过),给出a1-an,求 ∑ ∣ a [ i ] − a [ k [ i ] ] ∣ \sum |a[i]-a[k[i]]| ∑∣a[i]−a[k[i]]∣ 的最小值,k分别是p和q序列中的一个(p和q序列都要算入求和)
方法:
通过画线段表示数字大小可以发现,将数组排序后,每四个数字可以两两交换,因此最佳情况是算上所有排序后的a[i] - a[i - 3]求和,但是假如n=10,则会出现剩下6个的情况,因此6个的情况也要算上(是6个在前好还是6个在后一组好?因此要dp时把6个的情况也算上),但是一旦到8个一分的情况,法一:下面是重点:由于8个可以分成两个四个,所以没有必要计算(dp两层和一层的效果是一样的),这种贪心思想把计算dp[i]时本来的任意长度的表达式:
d
p
[
i
]
=
m
i
n
(
d
p
[
i
]
,
d
p
[
j
]
+
2
∗
(
a
[
i
]
−
a
[
j
+
1
]
)
)
dp[i] = min(dp[i],dp[j] + 2 * (a[i] - a[j + 1]))
dp[i]=min(dp[i],dp[j]+2∗(a[i]−a[j+1]))
变换成了:
d
p
[
i
]
=
m
i
n
(
d
p
[
i
−
4
]
+
2
∗
(
a
[
i
]
−
a
[
i
−
3
]
)
,
d
p
[
i
−
6
]
+
2
∗
(
a
[
i
]
−
a
[
i
−
5
]
)
)
dp[i] = min(dp[i-4] + 2 * (a[i] - a[i - 3]),dp[i - 6] +2 * (a[i] - a[i - 5]))
dp[i]=min(dp[i−4]+2∗(a[i]−a[i−3]),dp[i−6]+2∗(a[i]−a[i−5]))
每一种情况只有两个子情况,几乎相当于O(n)
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 200020;
const int INF = 0x3f3f3f3f;
ll dp[maxn],n,t,a[maxn];
int main()
{
scanf("%lld",&t);
while(t --)
{
scanf("%lld",&n);
for(int i = 1;i <= n;i ++)
{
scanf("%lld", &a[i]);
dp[i] = INF;
}
sort(a + 1, a + n + 1);
dp[4] = a[4] - a[1];
dp[0] = 0;
for(int i = 6;i <= n;i += 2)
{
dp[i] = min(dp[i - 4] + (a[i] - a[i - 3]),dp[i - 6] + (a[i] - a[i - 5]));
}
cout << 2 * dp[n] << endl;
}
return 0;
}
法二:注意到原始的任意长度的dp式关于j的这一维可以通过优先队列优化,即:
d
p
[
i
]
=
m
i
n
(
d
p
[
i
]
,
m
i
n
(
d
p
[
j
]
−
a
[
j
+
1
]
∗
2
)
+
2
∗
a
[
i
]
)
dp[i] = min(dp[i],min(dp[j] -a[j + 1] * 2) +2 * a[i])
dp[i]=min(dp[i],min(dp[j]−a[j+1]∗2)+2∗a[i])
而j的这一维之前都出现过,因此可以在计算i时插入队列,代码如下:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn = 200020;
const int INF = 0x3f3f3f3f;
ll dp[maxn];
ll a[maxn];
ll n,t;
priority_queue<ll,vector<ll>,greater<ll>> que;
int main()
{
scanf("%lld",&t);
while(t --)
{
scanf("%lld",&n);
for(int i = 1;i <= n;i ++)
{
scanf("%lld",&a[i]);
dp[i] = INF;
}
sort(a + 1,a + n + 1);
dp[4] = a[4] - a[1];
dp[6] = a[6] - a[1];
que.push(dp[4] - a[5]);
for(int i = 8;i <= n;i += 2)
{
ll tmp = que.top();
dp[i] = min(dp[i],(ll)tmp + a[i]);
que.push(dp[i - 2] - a[i - 1]);
}
while(que.size()) que.pop();
printf("%lld\n",2*dp[n]);
}
return 0;
}
注意这里乘2是最后统一乘的,一开始计算的都是一倍的a[i]差值
PS:这里n的总和才2e5,因此memset肯定会超时的,这种用不到整个数组的情况要用循环初始化