P3620 [APIO/CTSC2007] 数据备份——解题报告

6 篇文章 0 订阅
3 篇文章 0 订阅

一、题目链接

P3620 [APIO/CTSC2007] 数据备份

二、题目大意

在一条数轴上有 n n n个点,每相邻两点之间构成一个距离,选择互不相邻的 K K K个距离,使得这 K K K个距离之和最小。

数据范围: n ≤ 100000 n \leq 100000 n100000 1 ≤ k ≤ n 2 1 \leq k \leq \frac{n}{2} 1k2n

三、题目分析

  1. 因为要找 k k k个的和最小,如果不考虑题目中互不相邻的条件,我们可以直接贪心的寻找前 i i i个最小的距离。

  2. 而此时要求互不相邻的情况,我们假设此时剩下的距离中最小的是 X [ i ] X[i] X[i],如果选择 X [ i ] X[i] X[i],势必会影响 X [ i − 1 ] X[i-1] X[i1] X [ i + 1 ] X[i+1] X[i+1]的选择情况,所以我们将左右两段距离 X [ i − 1 ] X[i-1] X[i1] X [ i + 1 ] X[i+1] X[i+1]同时进行考虑。

  3. 因为 K = 1 K=1 K=1直接选择 X [ i ] X[i] X[i] 即可, K > 2 K>2 K>2时的情况可以由 K = 2 K=2 K=2同理得到。此时考虑 K = 2 K=2 K=2有几种情况:

    • 如果我们选择了 X [ i ] X[i] X[i],那么 X [ i − 1 ] X[i-1] X[i1] X [ i + 1 ] X[i+1] X[i+1]都不能选择了,将选择除这三个以外的最小值。答案为 X [ i ] + M i n X[i]+Min X[i]+Min
    • 如果我们选择了 X [ i − 1 ] X[i-1] X[i1] X [ i + 1 ] X[i+1] X[i+1],那么就不能选择 X [ i ] X[i] X[i]。答案为 X [ i − 1 ] + X [ i + 1 ] X[i-1]+X[i+1] X[i1]+X[i+1]
    • 如果我们只选择 X [ i − 1 ] X[i-1] X[i1] X [ i + 1 ] X[i+1] X[i+1]中的一个,再选择除这三个以外的最小值,那么答案一定大于 X [ i ] + M i n X[i]+Min X[i]+Min。因为 X [ i − 1 ] X[i-1] X[i1]和X[i+1]都大于X[i]。
  4. 所以最后我们发现,我们只有两种答案:

    (1) X [ i ] + M i n X[i]+Min X[i]+Min(2) X [ i − 1 ] + X [ i + 1 ] X[i-1]+X[i+1] X[i1]+X[i+1]

    那么我们如何判断这两个答案的大小呢?因为最小值显然会根据取数的不同发生变化,所以我们需要一个二叉堆来维护最小值(优先队列也可)。

  5. 我们发现,只有 M i n < X [ i − 1 ] + X [ i + 1 ] − X [ i ] Min<X[i-1]+X[i+1]-X[i] Min<X[i1]+X[i+1]X[i]的时候,答案(1)最小。还有一个巧合的地方在于, X [ i ] + X [ i − 1 ] + X [ i + 1 ] − X [ i ] = X [ i − 1 ] + X [ i + 1 ] X[i]+X[i-1]+X[i+1]-X[i]=X[i-1]+X[i+1] X[i]+X[i1]+X[i+1]X[i]=X[i1]+X[i+1]即答案(2)。

  6. 因为已经有了二叉堆,所以我们直接取出 X [ i ] X[i] X[i],删除 X [ i ] X[i] X[i] X [ i − 1 ] X[i-1] X[i1] X [ i + 1 ] X[i+1] X[i+1],再把 X [ i − 1 ] + X [ i + 1 ] − X [ i ] X[i-1]+X[i+1]-X[i] X[i1]+X[i+1]X[i]入堆,代替原来他们三个的位置成为一个新的距离即可。

    • M i n < X [ i − 1 ] + X [ i + 1 ] − X [ i ] Min<X[i-1]+X[i+1]-X[i] Min<X[i1]+X[i+1]X[i],则堆顶即为 M i n Min Min,答案为 X [ i ] + M i n X[i]+Min X[i]+Min
    • M i n > X [ i − 1 ] + X [ i + 1 ] − X [ i ] Min>X[i-1]+X[i+1]-X[i] Min>X[i1]+X[i+1]X[i],则堆顶为 X [ i − 1 ] + X [ i + 1 ] − X [ i ] X[i-1]+X[i+1]-X[i] X[i1]+X[i+1]X[i],答案为 X [ i − 1 ] + X [ i + 1 ] X[i-1]+X[i+1] X[i1]+X[i+1]

四、题目细节:

  • 因为要实现6中的操作,所以我们需要一个堆,以及一个链表。
  • 为了保证能够删掉堆和链表中的正确元素,我们需要两个指针数组来从堆指向链表和从链表指向堆。
  • 因为首尾两个距离没有 i − 1 i-1 i1 i + 1 i+1 i+1,所以我们人为添加一个正无穷在一头一尾表示访问越界

五、正解程序:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>

using namespace std;
typedef long long ll;
const ll maxn=100010;
struct node
{
	ll val;
	ll pos;//指针1
	ll prev,next;
}D[3*maxn];
struct ano//手动堆的实现
{
	ll val;
	ll pos;//指针2
}heap[3*maxn];
ll n,K,count1=0,head,tail,tot=0;
ll num[maxn];
void init()
{
	tot=2;
	head=1;tail=2;
	D[head].next=tail;
	D[head].val=0x7fffffff;//设置边界
	D[tail].val=0x7fffffff;//设置边界
	D[tail].prev=head;
}
void remove(ll p)
{
	D[D[p].prev].next=D[p].next;
	D[D[p].next].prev=D[p].prev;
}
ll insert(ll p,ll val)
{
	ll q=++tot;
	D[q].val=val;
	D[D[p].next].prev=q;
	D[q].next=D[p].next;
	D[p].next=q;
	D[q].prev=p;
	return q;
}
void up(ll p)
{
	while(p>1)
	{
		if(heap[p].val<heap[p/2].val)
		{
			swap(D[heap[p].pos].pos,D[heap[p/2].pos].pos);
			//堆更新,链表指向堆的指针也要更新
			swap(heap[p],heap[p/2]);
			p>>=1;
		}
		else
			break;
	}
	
}
void Insert(ano x)
{
	heap[++count1]=x;
	D[x.pos].pos=count1;
	up(count1);
}
void down(ll p)
{
	ll s=p*2;
	while(s<=count1)
	{
		if(s<n && heap[s].val>heap[s+1].val)
			s++;
		if(heap[s].val<heap[p].val)
		{
			swap(D[heap[s].pos].pos,D[heap[p].pos].pos);
			//堆更新,链表指向堆的指针也要更新
			swap(heap[s],heap[p]);
			p=s;
			s=p*2;
		}
		else
			break;
	}
}
void Remove(ll p)
{
	D[heap[count1].pos].pos=p;
	heap[p]=heap[count1--];
	up(p);down(p);
}
int main()
{
	scanf("%lld%lld",&n,&K);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&num[i]);
	n--;
	init();
	ano temp;
	temp.pos=1;temp.val=D[head].val;
	Insert(temp);
	temp.pos=2;temp.val=D[tail].val;
	Insert(temp);
	ll lastone=1;
	for(ll i=1;i<=n;i++)
	{
		ano temp;
		temp.pos=insert(lastone,num[i+1]-num[i]);
		lastone=temp.pos;
		temp.val=num[i+1]-num[i];
		Insert(temp);
	}
	ll ans=0;
	for(ll i=1;i<=K;i++)
	{ 
		ll f1=D[heap[1].pos].prev,f2=D[heap[1].pos].next;
		ll t1=D[f1].val,t2=D[f2].val,tp=heap[1].val,used=heap[1].pos;
		ll flag=D[f1].prev;
		ans+=tp;
		//删除三个数
		remove(used);remove(f1);remove(f2);
		Remove(1);Remove(D[f1].pos);Remove(D[f2].pos);
		
		ano temp;
		temp.pos=insert(flag,t1+t2-tp);
		temp.val=t1+t2-tp;
		Insert(temp);
	}
	printf("%lld",ans);
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值