題目大意:
直線上有n個點,選擇2k個互不相同的點,使他們兩兩配對,求所有點對的最小距離和。
題目分析:
(先扯一波淡)
一開始覺得n*n *k的dp可做,恩(點頭)
然後看了一眼數據範圍……
這個dp可優化到n*k,恩(點頭)
然後再看一眼數據範圍……
換個做法,網絡流也能跑,恩(點頭)
然後建完圖跑一下……
行了我AFO了
很顯然我們選擇的每一對點必須相鄰,否則相鄰的點對一定比不相鄰的點對更優。
那麼可選的點對就只有n-1個了。
我們貪心的每次選擇代價最小的點對,顯然這是不對的,所以就要用可修正的貪心策略。
假設我們選擇了a[i]-a[i-1]這一對點,那麼a[i-1]-a[i-2]和a[i+1]-a[i]顯然就選不了了,
所以要把這兩個點從中刪去,但是有可能更優的方案使選擇這兩個而不選擇a[i]-a[i-1]。
所以我們就把這三個小段合併成爲一個小段,變成(a[i+1]-a[i])+(a[i-1]-a[i-2])-(a[i]-a[i-1])。
(有人發現這樣做之後,a[i+1]-a[i]和a[i-1]-a[i-2]總是同時被選,這是正確的,因爲如果你只選擇a[i+1]-a[i],而不選擇a[i-1]-a[i-2]和a[i]-a[i-1],那麼你只選擇a[i]-a[i-1]一定比只選擇a[i+1]-a[i]更優,所以當a[i]-a[i-1]被選之後,a[i+1]-a[i]和a[i-1]-a[i-2]一定使同時被選)
當你選擇了這個新的點對的時候就相當於自動刪去了a[i]-a[i-1]並且選擇了他旁邊的兩個,這就相當於網絡流中退流的過程。
剛纔的那個東西就用雙向鏈表維護一下,刪除和合並操作都比較簡單。
每次選擇最優的答案可以用優先隊列維護(對於寫堆的童鞋,雖然泥萌很膩害……但是STL真是懶人神器啊=。=)
代碼如下:
#include <cstdio>
#include <queue>
#define N 120000
using namespace std;
const int INF=0x3f3f3f3f;
struct List{
int pre,nes,len,num;
bool operator < (const List &c) const { return len>c.len; }
}a[N];
bool vis[N];
int n,k,ans;
priority_queue<List> dl;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1,x=-INF,y;i<=n;i++)
{
scanf("%d",&y);
a[i].pre=i-1;
a[i].nes=i+1;
a[i].len=y-x;
a[i].num=i;
x=y;
dl.push(a[i]);
}
a[n+1].pre=n;
a[n+1].len=INF;
dl.push(a[n+1]);
while(k--)
{
while(vis[dl.top().num]) dl.pop();
int c=dl.top().num;
dl.pop();
ans+=a[c].len;
vis[a[c].pre]=true;
vis[a[c].nes]=true;
a[c].len=a[a[c].pre].len+a[a[c].nes].len-a[c].len;
a[c].pre=a[a[c].pre].pre;
a[c].nes=a[a[c].nes].nes;
a[a[c].pre].nes=c;
a[a[c].nes].pre=c;
dl.push(a[c]);
}
printf("%d\n",ans);
return 0;
}