1. 程式人生 > 其它 >【洛谷3229】[HNOI2013] 旅行(單調佇列)

【洛谷3229】[HNOI2013] 旅行(單調佇列)

給定一個 $1\sim n$ 的排列 $a_{1\sim n}$ 和一個長度為 $n$ 的 $01$ 序列 $b_{1\sim n}$。要求將序列劃分為恰好 $m$ 段,使得每一段 $b_i$ 中 $0$ 和 $1$ 個數差的絕對值的最大值最小。在此前提下,記每一段末尾的 $a_i$ 為 $q_{1\sim m}$,求字典序最小的 $q$。

題目連結

  • 給定一個 \(1\sim n\) 的排列 \(a_{1\sim n}\) 和一個長度為 \(n\)\(01\) 序列 \(b_{1\sim n}\)
  • 要求將序列劃分為恰好 \(m\) 段,使得每一段 \(b_i\)\(0\)\(1\) 個數差的絕對值的最大值最小。
  • 在此前提下,記每一段末尾的 \(a_i\)\(q_{1\sim m}\),求字典序最小的 \(q\)
  • \(1\le n\le 5\times10^5\)\(1\le m\le2\times10^5\)

最小的差值

\(op_i=\begin{cases}1&b_i=1,\\-1&b_i=0\end{cases}\)

,並設 \(s_i=\sum_{k=1}^iop_k\)

顯然,最小的差值必然大於等於 \(\lceil\frac{s_n}l\rceil\)

\(t=\lceil\frac{s_n}l\rceil\),如果 \(t>0\),肯定存在若干個位置滿足 \(s_i\) 分別為 \(t,2t,3t,\cdots\),分別取這些位置作為段末尾,最小的差值可以取到 \(t\)

如果 \(t=0\),若存在大於等於 \(m\) 個位置滿足 \(s_i=0\) 那麼最小的差值就能取到 \(0\),否則只能取到 \(1\)

這樣一來我們就確定了最小的差值(記作 \(g\)),接下來就是要在此前提下求出字典序最小的 \(q\)

最小的字典序

因為要讓字典序最小,依次考慮每一項,肯定是在合法的項中選擇最小的那一項。

如果 \(g=0\),我們只能在 \(s_i=0\) 的位置中選擇(假設有 \(c\) 個),只要初始將前 \(c-m\) 個元素加入單調佇列,然後每加入一個元素就取出隊首輸出即可。

對於一般情況,假設我們當前在填第 \(i\) 項,上一段末尾位置為 \(lst\),那麼對於一個位置 \(x\),需要滿足以下條件:

  • 因為末尾位置遞增,所以 \(x > lst\)
  • 因為之後還要劃分出 \(m-i\) 段,所以 \(x\le n-m+i\)
  • 因為差值不能超過答案 \(g\) ,所以 \(s_{lst}-g\le s_x\le s_{lst}+g\)
  • 因為剩下的位置需要能在 \(m-i\) 段以內劃分完,所以 \(|s_n-s_x|\le (m-i)\times g\)

對於前兩個條件,因為 \(lst,n-m+i\) 都是單調遞增的,依舊使用單調佇列維護,只要每次更新 \(i\) 時加入新的合法元素,更新 \(lst\) 時彈出開頭的不合法元素即可。

對於後兩個條件,發現只與 \(s_x\) 有關,因此可以實際上可以對於不同的 \(s_x\) 分別開一個單調佇列,每次列舉 \([s_{lst}-g,s_{lst}+g]\) 範圍內的這些單調佇列,用其中合法的那些更新答案。

具體實現中我們需要討論 \(m\) 個末尾位置,對於每個位置至多需要列舉 \(2g+1\) 個單調佇列,因為 \(mg≈s_n\le n\),所以總複雜度 \(O(n)\)

程式碼:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 500000
#define INF (int)1e9
using namespace std;
int n,m,a[N+5],s[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void write(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc(' ');}
}using namespace FastIO;
int pre[N+5],nxt[N+5];struct Q
{
	int H,T;I void A(CI x) {W(T&&a[T]>a[x]) T=pre[T];T?(pre[x]=T,nxt[T]=x,T=x):(H=T=x);}//單調佇列
	I void D(CI x) {H==x&&(pre[H=nxt[H]]=0,!H&&(H=T=0));}//彈出隊首
}q[2*N+5];
int main()
{
	RI i,x,c=0;for(read(n,m),a[0]=INF,i=1;i<=n;++i) read(a[i],x),!(s[i]=s[i-1]+(x?1:-1))&&++c;
	RI g=(abs(s[n])+m-1)/m;!g&&c<m&&++g;if(!g)//如果g=0
	{
		for(c=0,i=1;i<=n;++i) !s[i]&&(a[++c]=a[i]);for(i=1;i<=c-m;++i) q[0].A(i);//加入前c-m個元素
		for(i=1;i^m;++i) q[0].A(c-m+i),write(a[q[0].H]),q[0].D(q[0].H);return write(a[n]),clear(),0;//加一個元素,輸出一次隊首
	}
	RI o=1,j,t,id,k=0;for(i=1;i<=n-m;++i) q[n+s[i]].A(i);for(i=1;i^m;++i)//加入前n-m個元素
	{
		q[n+s[n-m+i]].A(n-m+i),t=INF;//加入第n-m+i個元素
		for(j=max(k-g,-n);j<=min(k+g,n);++j) abs(s[n]-j)<=g*(m-i)&&t>a[q[n+j].H]&&(t=a[id=q[n+j].H]);//列舉合法的單調佇列更新答案
		write(t),k=s[id];W(o<=id) q[n+s[o]].D(o),++o;//彈出開頭的不合法元素
	}return write(a[n]),clear(),0;//最後的末尾必須是a[n]
}
敗得義無反顧,弱得一無是處