P3229 [HNOI2013]旅行 题解

洛谷传送门

这个题用到了单调队列的优化,但是精髓绝不止在于此。+_+

Solution

我们先不管字典序怎么办,想想怎么求最小的 ansans

首先,因为要的是绝对值,那么考虑将 00 化为 -1−1 ,也就是将没有景点的城市看作会减少一个景点的城市(雾

设 b_ibi​ 表示城市 a_iai​ 是否有景点,有为 11 ,否则为 -1−1,sum_{i}sumi​ 表示 i+1i+1 到 nn 的后缀和,也就是 sum_{i}=b_{i+1}+b_{i+2}+\cdots+b_nsumi​=bi+1​+bi+2​+⋯+bn​ ,那么可以证明答案最小不小于 \lceil \frac {|sum_0|}m\rceil⌈m∣sum0​∣​⌉ 。

如果全部是 11 或 -1−1 ,显然平均的将 sum_0sum0​ 分为 mm 份最好;如果同时有 1,-11,−1 ,将相邻的两个 1,-11,−1 合并可以得到 00 即抵消,最后还是剩下 sum_0sum0​ 个 11 或 -1−1 ,就和第一种一样了。

但是还是有一种特殊情况的,当 sum_0=0sum0​=0 且 sum_i=0sumi​=0 的 ii 的数量不足 mm 个,此时答案就是 11 ,因为分不了那么多段。

既然 ansans 已经能够求出来了,那么就可以回来看看字典序

将 ans=0ans=0 和 ans\not =0ans=0 分开考虑。

当 ans=0ans=0 时,即 sum_0=0sum0​=0 并且 sum_i=0sumi​=0 的 ii 的数量 \geq m≥m ,那么就在这些 ii 中跑一遍单调队列即可。

再具体就是维护队列里 a_iai​ 从小到大,按照输入顺序入队 ,找到字典序最小的 m-1m−1 个。(因为第 mm 个城市必定是 a_nan​ )

当 ans\not= 0ans=0 时,我们假设第 ii 段结尾的是 lstlst ,那么 i+1i+1 段的结尾后一个位置的后缀和 ss 需要满足 |sum_{lst}-s|\leq ans\Rightarrow sum_{lst}-ans\leq s\leq sum_{lst}+ans∣sumlst​−s∣≤ans⇒sumlst​−ans≤s≤sumlst​+ans 。

那么选择按 sum_isumi​ 分类,对每一种 sum_isumi​ 开一个单调队列,维护队列里 a_iai​ 单调递增,选的时候从 sum_{lst}-anssumlst​−ans 到 sum_{lst}+anssumlst​+ans 枚举选择,注意我们还需要判断 \lceil \frac {|s|}{m-i}\rceil\leq ans⌈m−i∣s∣​⌉≤ans 。

注意:这题卡空间,所以要手动写队列。(●ˇ∀ˇ●)

Code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define B N

using namespace std;
const int N=500010;

template<typename T>void read(T &x){
    x=0;bool f=0;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=1;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
    if(f) x=-x;
}

struct node{
    int v,id;
};
int cnt;
struct Node{
    node p;
    int pre,nxt;
}tr[N<<1];
int newcode(node p,int pre,int nxt){tr[++cnt]=(Node){p,pre,nxt}; return cnt;}

struct queue{
    int siz,hd,tl;
    bool empty(){return !siz;}

    void push_front(node p){
        if(!siz) hd=tl=newcode(p,0,0);
        else tr[hd].pre=newcode(p,0,hd),hd=tr[hd].pre;
        ++siz;
    }

    void push_back(node p){
        if(!siz) hd=tl=newcode(p,0,0);
        else tr[tl].nxt=newcode(p,tl,0),tl=tr[tl].nxt;
        ++siz;
    }

    void pop_front(){siz--,hd=tr[hd].nxt;}
    void pop_back(){siz--,tl=tr[tl].pre;}

    node front(){return tr[hd].p;}
    node back(){return tr[tl].p;}
}q[N<<1];

int tot,head[N<<1],nxt[N<<1];
node to[N<<1];
void add(int u,node p){
    to[++tot]=p;
    nxt[tot]=head[u];
    head[u]=tot;
}

int n,m,lst,ct[N],sum[N],rest[N];//rest表示休息点的后缀和 sum表示相加的后缀和
int calc(){
    int ans=0;
    for(int i=n;i>=1;i--){
        if(!sum[i]) ans++,rest[i]=1;
        rest[i]+=rest[i+1];
    }
    if(ans>=m) return 0;
    else return 1;
}

void push(int u,node p){
    while(!q[u].empty()&&p.v<q[u].back().v) q[u].pop_back();
    q[u].push_back(p);
}

node calc(int u,int lim){
    for(int i=head[u];i&&to[i].id<=lim;i=nxt[i]) push(u,to[i]),head[u]=i;
    while(!q[u].empty()&&q[u].front().id<=lst) q[u].pop_front();
    return q[u].empty()?(node){N,N}:q[u].front();
}

node min(node a,node b){return a.v<b.v?a:b;}

int main(){
    read(n); read(m);
    for(int i=1;i<=n;i++) read(ct[i]),read(sum[i-1]),sum[i-1]=(sum[i-1])?1:-1;
    for(int i=n-1;i>=0;i--) sum[i]+=sum[i+1];
    for(int i=n;i>=1;i--) add(sum[i]+B,(node){ct[i],i});
    int S=sum[0],d=(S!=0)?(int)ceil(1.0*abs(S)/(1.0*m)):calc();
    if(d){
        for(int i=1;i<m;i++){
            node ans=(node){N,N};
            for(int j=S-d+B;j<=S+d+B;j++){
                if(ceil(abs(1.0*j-B)/(1.0*m-i))<=d) ans=min(ans,calc(j,n-(m-i)));
            }
            printf("%d ",ans.v);
            lst=ans.id,S=sum[lst];
        }
    }
    else{
        for(int j=1,i=head[B];j<m;j++){
            for(;i&&rest[to[i].id]-1>=m-j;i=nxt[i]) push(B,to[i]);
            printf("%d ",q[B].front().v);
            q[B].pop_front();
        }
    }
    printf("%d\n",ct[n]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值