题解 洛谷P3620 数据备份
作者:互联网
题解 洛谷P3620 数据备份
转化题意:
不难想到,选的电缆连接的肯定是相邻的两个点。
这样的话,我们直接把点去掉,取而代之的是 \(n-1\) 条边,在其中选 \(k\) 个互不相邻的边使总长度最小。
那么不妨试试,先把所有边放进小根堆里,每次取出堆顶,只要堆顶相邻的边没被选过,就加进答案里,这样是否可行呢?
显然,这道题直接贪心是错的,如样例:
输入
5 2
1
3
4
6
12
输出
4
若直接贪心,则会选到 2-3 和 4-5 这两条边,但实际上选 1-2 和 3-4 这两条边更优。
于是我们需要一个可以使他重新选到相邻边的东西,设边集为 b ,则可以在堆顶 \(b_i\) 被取出是往堆里添加一个量: \(b_{i\_last}+b_{i\_next}-b_i\) ,这样的话,如果 \(b_{i\_last}+b_{i\_next}-b_i\) 在堆顶并被取出的话,则可以顶替原有的 \(b_i\) ,使之等价于取了 \(b_{i\_last}+b_{i\_next}\) 。
那为什么不考虑只用左边或右边的一个来替换原有 \(b_i\) 呢?若左/右的边能够替换 \(b_i\) 则意味着它的解比使用 \(b_i\) 更优。但实际上,只有一个元素时,这意味着 \(b_{i\_last}>b_i\) 或 \(b_{i\_next}>b_i\) ,而此时, \(b_{i\_last}\) 或是 \(b_{i\_next}\) 一定在 \(b_i\) 之前就成为堆顶并被取出,所以就不需再考虑了。
那么现在怎么确切地随时知道 \(b_{i\_last}+b_{i\_next}\) 的位置呢?可以用双向链表来实现。每次选中堆顶,链表中其左右相邻的元素就删去,同时在原有的位置上插入一个值为 \(b_{i\_last}+b_{i\_next}-b_i\) 的点,即可随时得到 \(i\_last\) 与 \(i\_next\) 了。
CODE
const int mn=1e5+10;
const int mm=1e5+10;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int fni=0xc0c0c0c0;
int n,m,b[mn];
struct node{
int dat,l,r;
}p[mn];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
inline void read_init(){
n=read<int>();m=read<int>();
}
inline void f(int x){
p[x].l=p[p[x].l].l;
p[x].r=p[p[x].r].r;
p[p[x].l].r=x;
p[p[x].r].l=x;
}
bool vis[mn];
int main(){
read_init();
int las=read<int>(),now;
for(int i=1;i<n;i++){
now=read<int>();
p[i].dat=now-las;
las=now;
p[i].l=i-1;
p[i].r=i+1;
q.push(make_pair(p[i].dat,i));
}
int ans=0;
p[0].dat=p[n].dat=inf;
for(int i=1;i<=m;i++){
while(vis[q.top().second])q.pop();
int dat=q.top().first,id=q.top().second;
q.pop();ans+=dat;
vis[p[id].l]=vis[p[id].r]=1;
p[id].dat=p[p[id].l].dat+p[p[id].r].dat-p[id].dat;
q.push(make_pair(p[id].dat,id));
f(id);
}
write(ans,1);
return 0;
}
标签:洛谷,int,题解,next,dat,read,P3620,last,const 来源: https://www.cnblogs.com/ex-asbable/p/16504279.html