原题传送门
首先转化题意,可以用贪心策略证明肯定是相邻的两个点建立电缆
然后把问题转化成 a i = s i + 1 − s i a_i=s_{i+1}-s_i ai=si+1−si, a 1 , a 2 , . . . , a n − 1 a_1,a_2,...,a_{n-1} a1,a2,...,an−1中取 k k k个不相邻的数使得和最小
首先非常明显的 O ( n k ) naiveDP O(nk)\text{naiveDP} O(nk)naiveDP,可以拿到60分的好成绩
然后考虑优化,dp上状态已经 O ( n k ) O(nk) O(nk),到顶了,除非设计更妙的 O ( n ) O(n) O(n)状态,但是数据范围告诉我们,需要一个 O ( n l o g n ) O(nlogn) O(nlogn)的做法
于是想到反悔贪心
从头思考,一个非常 n a i v e naive naive的贪心,维护优先队列,每次取最小的,并且把左右两个打上标记,因为相邻的两个不能都选,这样取 k k k次即可
h a c k hack hack显然,但是我们可以增添一个反悔机制,每次从优先队列取出一个未标记的值时,首先这个值肯定合理并且当前最优,那么就加入答案,但是这个值不一定全局最优,所以以后可能反悔,那么怎么反悔呢,当然就是不选这个值了,改选这个值左右两边的两个值
具体流程就是对于一个取出的值 a j a_j aj,统计答案,并且将值 a j − 1 + a j + 1 − a j a_{j-1}+a_{j+1}-a_j aj−1+aj+1−aj加入优先队列,这样依然是做 k k k次,因为每做一次就会多一个数
然后不要忘了打标记,这里我们需要用双向队列维护一下左右两边的值
Code:
#include
#define maxn 100010
using namespace std;
struct node{
int num, val;
bool operator < (const node &x) const{ return x.val < val; }
};
priority_queue <node> q;
struct data{
int l, r, val;
}a[maxn];
int n, k, vis[maxn], ans;
inline int read(){
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
return s * w;
}
void del(int x){
a[x].l = a[a[x].l].l, a[x].r = a[a[x].r].r,
a[a[x].l].r = x, a[a[x].r].l = x;
}
int main(){
n = read(), k = read();
int Last = read();
for (int i = 1; i < n; ++i){
int x = read();
a[i].val = x - Last, Last = x, a[i].l = i - 1, a[i].r = i + 1;
q.push((node){i, a[i].val});
}
a[0].val = a[n].val = 1e9;
for (int i = 1; i <= k; ++i){
while (vis[q.top().num]) q.pop();
node tmp = q.top(); q.pop();
ans += tmp.val;
vis[a[tmp.num].l] = vis[a[tmp.num].r] = 1;
a[tmp.num].val = a[a[tmp.num].l].val + a[a[tmp.num].r].val - a[tmp.num].val;
q.push((node){tmp.num, a[tmp.num].val});
del(tmp.num);
}
printf("%d\n", ans);
return 0;
}