【洛谷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\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]
}