1. 程式人生 > 其它 >Codeforces 1175G - Yet Another Partiton Problem(李超線段樹)

Codeforces 1175G - Yet Another Partiton Problem(李超線段樹)

李超線段樹優化 dp+可撤銷李超線段樹+李超線段樹的合併,李超線段樹的綜合應用,李超線段樹的毒瘤題,勢能分析複雜度

Codeforces 題面傳送門 & 洛谷題面傳送門

這是一道李超線段樹的毒瘤題。

首先我們可以想到一個非常 trivial 的 DP:\(dp_{i,j}\)​ 表示前 \(i\)​ 個數劃分成 \(j\)​ 段的最小代價,那麼顯然 \(dp_{i,j}=\min\limits_{l<i}\{dp_{l,j-1}+(i-l)·\max\limits_{t=l+1}^ia_t\}\),這樣暴力 DP 是 \(n^2k\) 的,一臉過不去。

考慮優化,注意到這裡涉及一個 \(\max\),注意到在我們 DP 掃一遍 \(a_i\) 的過程中,\(a_i\) 的最大值顯然是成段分佈的,且我們可以通過單調棧求出這些段的左端點和右端點,因此我們考慮將 \(i\)

​ 前面的部分進行分段,每一段用一個三元組 \((L,R,M)\) 表示,表示 \(\forall l\in[L,R]\) 都有 \(\max\limits_{t=l+1}^ia_t=M\),那麼對於 \([L,R]\) 這個區間中的任何一個 \(l\),從 \(dp_{l,j-1}\) 轉移到 \(dp_{i,j}\) 的貢獻都是 \(dp_{l,j-1}+(i-l)·M\),也就是說上式可以寫成 \(dp_{i,j}=\min\limits_{(L,R,M)}\{\min\limits_{l=L}^Rdp_{l,j-1}+(i-l)·M\}\),注意到對於一個固定的 \(M\),我們只需要求出 \(dp_{l,j-1}-lM\)
的最小值 \(mn\),這一段轉移到 \(dp_{i,j}\) 的貢獻就是 \(iM-mn\)。如果我們令 \(k=M,x=i,b=-mn\),那麼上式就可以寫成 \(kx+b\)。想到了什麼?沒錯,斜率優化,李超線段樹,我們考慮以原序列中的下標為下標建一棵李超線段樹,那麼每一段的貢獻就是一條直線,將它們插入李超線段樹,那麼查詢 \(dp_{i,j}\) 時就查詢第 \(i\) 位置的最小值即可。

不過注意到我們這個連續段也不是一成不變的,在單調棧維護最大值的段時還會出現彈棧操作,具體來說,當加入一個 \(a_i\) 時候我們會不斷彈出棧頂元素直到棧為空或棧頂元素 \(>a_i\),這樣帶來的副作用就是以這些彈出的這些元素為右端點的連續段全部都會消失,取而代之的是一個大連續段,滿足這個連續段中的最大值為 \(a_i\)

,那麼我們就需要在李超線段樹中刪除這些直線,注意到我們是按時間順序插入這些直線的,因此刪除時肯定也會刪除新插入的幾條直線,因此我們像線段樹分治那樣開一個棧維護操作序列然後不斷彈出棧頂元素並將李超線段樹上對應元素改回其以前的版本知道回到操作前的序列為止。還有一個問題,就是這一段的 \(dp_{l,j-1}-lM\) 也會改變,這個看似很好維護,實則比較困難。注意到我們是要對於新的 \(M\),求出 \(dp_{l,j-1}-lM\) 的最小值,如果我們再設 \(k=-l,x=M,b=dp_{l,j-1}\),那麼柿子可以寫作 \(kx+b\)。想到了什麼?沒錯你沒聽錯,還是李超線段樹,我們考慮對每個連續段再建一棵李超線段樹,維護這個區間中形如 \(-lx+dp_{l,j-1}\),那麼彈棧過程中這些李超線段樹就會合並,因此我們像 CF932F 那樣合併這些連續段對應的李超線段樹即可查詢 \(dp_{l,j-1}-lM\) 的最小值。

時間複雜度大概是 \(\mathcal O(nk\log n)\),具體證明大概就考慮每條直線在李超線段樹上的深度,顯然是不降的,而線段樹深度最多 \(\log n\),因此這些直線在一輪(求解第二維相同的 DP 值)中下移的次數最多 \(\log n\)

const int MAXN=2e4;
const int MAXV=2e4;
const int MAXK=100;
const int MAXP=MAXN*30;
const ll INF=0x3f3f3f3f3f3f3f3fll;
int n,k,a[MAXN+5];ll dp[MAXN+5][MAXK+5];
struct line{
	ll k,b;
	line(ll _k=0,ll _b=INF):k(_k),b(_b){}
	ll get(int x){return 1ll*k*x+b;}
} lns[MAXN*2+5];
int lcnt=0;
struct node{int ch[2],mx;} s[MAXP+5];
int rt[MAXN+5],R=0,ocnt=0,ncnt=0;
struct chg{int k,on,ori;} op[MAXP+5];
void deal(int k,int id,int o){
//	printf("deal %d %d %d\n",k,id,o);
	if(o) op[++ocnt]={k,o,s[k].mx};
	s[k].mx=id;
}
void insert(int &k,int l,int r,int v,int is){
	if(!k) return k=++ncnt,deal(k,v,is),void();int mid=l+r>>1;
	ll l1=lns[s[k].mx].get(l),r1=lns[s[k].mx].get(r),m1=lns[s[k].mx].get(mid);
	ll l2=lns[v].get(l),r2=lns[v].get(r),m2=lns[v].get(mid);
	if(l1<=l2&&r1<=r2) return;
	if(l2<=l1&&r2<=r1) return deal(k,v,is),void();
	if(m2<=m1){
		if(l2<=l1) insert(s[k].ch[1],mid+1,r,s[k].mx,is),deal(k,v,is);
		else insert(s[k].ch[0],l,mid,s[k].mx,is),deal(k,v,is);
	} else {
		if(l2<=l1) insert(s[k].ch[0],l,mid,v,is);
		else insert(s[k].ch[1],mid+1,r,v,is);
	}
}
int merge(int x,int y,int l,int r){
	if(!x||!y) return x+y;insert(x,l,r,s[y].mx,0);
	int mid=l+r>>1;//printf("%d %d\n",s[x].mx,s[y].mx);
	s[x].ch[0]=merge(s[x].ch[0],s[y].ch[0],l,mid);
	s[x].ch[1]=merge(s[x].ch[1],s[y].ch[1],mid+1,r);
	return x;
}
ll query(int k,int l,int r,int p){
	if(!k) return INF;int mid=l+r>>1;
	if(l==r) return lns[s[k].mx].get(p);
	return min((p<=mid)?query(s[k].ch[0],l,mid,p):query(s[k].ch[1],mid+1,r,p),
	lns[s[k].mx].get(p));
}
int stk[MAXN+5],tp=0;
void clear(){
	memset(rt,0,sizeof(rt));
	for(int i=1;i<=ncnt;i++) s[i].ch[0]=s[i].ch[1]=s[i].mx=0;
	ncnt=lcnt=ocnt=tp=R=0;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	memset(dp,63,sizeof(dp));dp[0][0]=0;
	for(int j=1;j<=k;j++){
		clear();
		for(int i=1;i<=n;i++){
			lns[++lcnt]=line(-(i-1),dp[i-1][j-1]);
			insert(rt[i],1,MAXV,lcnt,0);
//			printf("lns[%d]={%lld,%lld}\n",lcnt,lns[lcnt].k,lns[lcnt].b);
			while(tp>0&&a[stk[tp]]<a[i]){
				while(ocnt>0&&op[ocnt].on==stk[tp]) s[op[ocnt].k].mx=op[ocnt].ori,ocnt--;
				rt[i]=merge(rt[i],rt[stk[tp]],1,MAXV);tp--;
			} lns[++lcnt]=line(a[i],query(rt[i],1,MAXV,a[i]));
//			printf("lns[%d]={%lld,%lld}\n",lcnt,lns[lcnt].k,lns[lcnt].b);
			insert(R,1,n,lcnt,i);dp[i][j]=query(R,1,n,i);
			stk[++tp]=i;
//			printf("%d %d %lld\n",i,j,dp[i][j]);
		}
	} printf("%lld\n",dp[n][k]);
	return 0;
}