1150: [CTSC2007]数据备份Backup
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 797 Solved: 333
[ Submit][ Status]
Description
Input
输入的第一行包含整数n和k,其中n(2 ≤ n ≤100 000)表示办公楼的数目,k(1≤ k≤ n/2)表示可利用的网络电缆的数目。接下来的n行每行仅包含一个整数(0≤ s ≤1000 000 000), 表示每个办公楼到大街起点处的距离。这些整数将按照从小到大的顺序依次出现。
Output
输出应由一个正整数组成,给出将2K个相异的办公楼连成k对所需的网络电缆的最小总长度。
Sample Input
1
3
4
6
12
Sample Output
HINT
上面的样例输入给出了前面描述的示例情形 对于每一个测试点,如果写到输出文件中的答案正确,则得到该测试点100%的分数,否则得零分。30%的输入数据满足n≤20。60%的输入数据满足n≤10 000。
贪心+堆。
首先对读入数据差分,题目就变成了:
从差分后的n-1个数中选出k个不相邻的数,使得他们的和最小。
对于“不相邻”的处理非常巧妙~
1.首先我们把所有的数放入堆中
2.取出最小的那个x
3.接下来把这条线段改成他左边的线段+右边的线段-x,插入堆中
4.把x左右两边的线段都删去
5.返回到2,重复k次这个过程
每次从堆中取出一个数,必然会使取出的总线段+1,并且满足都不相邻的条件。
贪心的来看,每次取出来的都是最小的,也就是使得取出的总线段数多一条的最小代价,因此满足总线段长度最小。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <queue>
#define inf 0x3f3f3f3f
#define mp make_pair
#define pa pair<int,int>
#define M 100000+5
using namespace std;
int n,m,len[M],pre[M],ne[M];
priority_queue<pa,vector<pa>,greater<pa> > q;
void read(int &tmp)
{
tmp=0;
char ch=getchar();
int fu=1;
for (;ch<'0'||ch>'9';ch=getchar())
if (ch=='-') fu=-1;
for (;ch>='0'&&ch<='9';ch=getchar())
tmp=tmp*10+ch-'0';
tmp*=fu;
}
int main()
{
read(n),read(m);
int x=0,y;
for (int i=1;i<=n;i++)
{
read(y);
len[i]=y-x;
pre[i]=i-1,ne[i]=i+1;
x=y;
}
int ans=0;
for (int i=2;i<=n;i++)
q.push(mp(len[i],i));
pre[2]=0,ne[n]=0;
for (int i=1;i<=m;i++)
{
while (q.top().first!=len[q.top().second])
q.pop();
int x=q.top().second,l=pre[x],r=ne[x];
ans+=len[x];
q.pop();
pre[ne[x]=ne[r]]=x;
ne[pre[x]=pre[l]]=x;
len[x]=l&&r?min(inf,len[l]+len[r]-len[x]):inf;
len[l]=len[r]=inf;
q.push(mp(len[x],x));
}
printf("%d\n",ans);
return 0;
}
感悟:
1.这道题在思路上非常巧妙:
如果要取一条线段两端的线段,那么这条线段就不能存在,于是线段长度变成左边+右边-当前线段;
而这个思路对于多条线段也是成立的,我们可以把合并好的多条线段一起来看,如果当前线段是3条线段合并成的:a+b-c,那么左边+右边-当前线段的时候相当于删去a,b,然后把c“复活”
2.代码的写法也很巧妙(orz zyf-zyf)
对于一条线段,记录左边和右边的端点,pre[],ne[];删除一个点的时候,无法直接从队列中取出,那么把他的值赋为inf,从队列中取出的时候发现队列中的长度与这个值不符,则直接跳过。
3.UPD:其实直接用set就能实现了