zoukankan      html  css  js  c++  java
  • 【洛谷3229】[HNOI2013] 旅行(单调队列)

    题目链接

    • 给定一个 (1sim n) 的排列 (a_{1sim n}) 和一个长度为 (n)(01) 序列 (b_{1sim n})
    • 要求将序列划分为恰好 (m) 段,使得每一段 (b_i)(0)(1) 个数差的绝对值的最大值最小。
    • 在此前提下,记每一段末尾的 (a_i)(q_{1sim m}),求字典序最小的 (q)
    • (1le nle 5 imes10^5)(1le mle2 imes10^5)

    最小的差值

    (op_i=egin{cases}1&b_i=1,\-1&b_i=0end{cases}),并设 (s_i=sum_{k=1}^iop_k)

    显然,最小的差值必然大于等于 (lceilfrac{s_n}l ceil)

    (t=lceilfrac{s_n}l ceil),如果 (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) 段,所以 (xle n-m+i)
    • 因为差值不能超过答案 (g) ,所以 (s_{lst}-gle s_xle s_{lst}+g)
    • 因为剩下的位置需要能在 (m-i) 段以内划分完,所以 (|s_n-s_x|le (m-i) imes g)

    对于前两个条件,因为 (lst,n-m+i) 都是单调递增的,依旧使用单调队列维护,只要每次更新 (i) 时加入新的合法元素,更新 (lst) 时弹出开头的不合法元素即可。

    对于后两个条件,发现只与 (s_x) 有关,因此可以实际上可以对于不同的 (s_x) 分别开一个单调队列,每次枚举 ([s_{lst}-g,s_{lst}+g]) 范围内的这些单调队列,用其中合法的那些更新答案。

    具体实现中我们需要讨论 (m) 个末尾位置,对于每个位置至多需要枚举 (2g+1) 个单调队列,因为 (mg≈s_nle 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]
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    可以
    全链路压测方案
    PyTestReport使用
    查看mysql的版本号
    Centos7 下的SVN安装与配置
    confluence中org.apache.tomcat.util.net.NioEndpoint$Acceptor.run Socket accept failed的解决方法
    CentOS7安装部署zabbix3.4操作记录
    Aasible中cryptography兼容性报错解决办法
    windows10中git-bash闪退的解决办法
    jira发送邮件报错
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3229.html
Copyright © 2011-2022 走看看