bzoj 1150 [CTSC2007]數據備份Backup 貪心+優先隊列

題目大意:
直線上有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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章