Solution -「APIO/CTSC 2007」「洛谷 P3620」資料備份
\(\mathcal{Description}\)
Link.
給定升序序列 \(\{x_n\}\) 以及整數 \(k\),在 \(\{x_n\}\) 中選出恰 \(k\) 對 \((x_i,x_j)\),使得不存在某個值出現次數多於一次,並最小化 \(\sum|x_i-x_j|\)。
\(\mathcal{Solution}\)
告訴我,你有一個錯誤的貪心 owo!
顯然 \((x_i,x_j)\) 是相鄰的兩個數。令 \(a_i=x_{i+1}-x_i\),問題轉化為選 \(k\) 個 \(a_i\) 使其和最小,並保證 \(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+1}\)(這裡的下標加減法指前驅後繼,因為有些數已經被刪掉了)。考慮再次選擇 \(a_i\) 時所表達的方案:
初始:
\[a_{i-2}~~~~a_{i-1}~~~~a_i~~~~a_{i+1}~~~~a_{i+2} \]
選 \(a_i\),此時答案 \(ans=a_i\);並重置 \(a_i\),刪前驅後繼:
\[a_{i-2}~~~~\cancel{a_{i-1}}~~~~a_{i-1}+a_{i+1}-a_i~~~~\cancel{a_{i+1}}~~~~a_{i+2} \]
再選 \(a_i\),此時答案 \(ans=a_i+a_{i-1}+a_{i+1}-a_i=a_{i-1}+a_{i+1}\),再重置,刪除:
\[\cancel{a_{i-2}}~~~~\cancel{a_{i-1}}~~~~a_{i-2}+a_{i+2}-a_{i-1}-a_{i+1}+a_i~~~~\cancel{a_{i+1}}~~~~\cancel{a_{i+2}} \]
可以發現,這與直接選 \(a_{i-2}\)
複雜度 \(\mathcal O(n\log n)\)。
\(\mathcal{Code}\)
#include <queue>
#include <cstdio>
typedef long long LL;
typedef std::pair<LL, int> pli;
const int MAXN = 1e5;
int n, K, pre[MAXN + 5], suf[MAXN + 5];
LL val[MAXN + 5];
bool rmd[MAXN + 5];
std::priority_queue<pli, std::vector<pli>, std::greater<pli> > heap;
inline int rint () {
int x = 0; char s = getchar ();
for ( ; s < '0' || '9' < s; s = getchar () );
for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
return x;
}
inline void rmpos ( const int u ) {
if ( ! u || u == n ) return ;
rmd[u] = true;
if ( pre[u] ) suf[pre[u]] = suf[u];
if ( suf[u] ^ n ) pre[suf[u]] = pre[u];
pre[u] = suf[u] = 0;
}
int main () {
n = rint (), K = rint ();
for ( int i = 0, p, las; i < n; ++ i ) {
p = rint ();
if ( i ) {
heap.push ( { val[i] = p - las, i } );
pre[i] = i - 1, suf[i] = i + 1;
}
las = p;
}
LL ans = 0;
val[0] = val[n] = 1ll << 60;
while ( K -- ) {
pli t = heap.top (); heap.pop ();
if ( rmd[t.second] ) { ++ K; continue; }
ans += t.first; LL nv = -t.first;
nv += val[pre[t.second]], rmpos ( pre[t.second] );
nv += val[suf[t.second]], rmpos ( suf[t.second] );
heap.push ( { val[t.second] = nv, t.second } );
}
printf ( "%lld\n", ans );
return 0;
}