输入的第一行包含整数n和k,其中n(2 ≤ n ≤100 000)表示办公楼的数目,k(1≤ k≤ n/2)表示可利用的网络电缆的数目。接下来的n行每行仅包含一个整数(0≤ s ≤1000 000 000), 表示每个办公楼到大街起点处的距离。这些整数将按照从小到大的顺序依次出现。
输出应由一个正整数组成,给出将2K个相异的办公楼连成k对所需的网络电缆的最小总长度。
上面的样例输入给出了前面描述的示例情形 对于每一个测试点,如果写到输出文件中的答案正确,则得到该测试点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就能实现了