BZOJ3141: [Hnoi2013]旅行(单调队列)

传送门

题解:
相当于给一个 ai={1,1} a i = { 1 , − 1 } 的序列,划分为 m m 段使得每段绝对值最小。
sum 为序列之和。
容易发现答案为 |sum|m | s u m | m

注意当 |sum|=0 | s u m | = 0 时有特殊情况。 我们记 si=ni=iai s i = ∑ i = i n a i ,下面证明这个结论。
1.当 |sum|=0 | s u m | = 0 ,且 si+1=0 s i + 1 = 0 的 位置 m ≥ m ,此时我们直接取 m m 个位置即可。
2.当|sum|=0,且 si+1=0 s i + 1 = 0 的位置 <m < m , 此时显然答案为 1 1

3.当|sum|0,此时答案为 |sum|m | s u m | m

证明:
如果 |sum|m | s u m | ≥ m ,把相邻的 1,1 − 1 , 1 合并,最后剩下一些 1 1 或者1,分为 m m 组。
否则情况变为2

然后 ans=0 a n s = 0 的情况直接特判做单调队列。

(以下除法取上整)
对于 ans0 a n s ≠ 0 的情况, 我们仍然采用贪心的方法,首先维护当前的可选集合,记当前序列总和为 sum s u m ,一个位置 i i 可选当且仅当:
1.|sumsi+1|ans
2. |si+1|m1ans | s i + 1 | m − 1 ≤ a n s
3. nkm1 n − k ≥ m − 1

前两个条件只跟 si+1 s i + 1 有关且为一段区间,我们可以每次把满足 3 3 的位置放入平衡树然后查最值。

不过这样不够优雅, 我们进一步发现可以暴力枚举si+1的取值,因为 O(ansm)=O(S) O ( a n s ∗ m ) = O ( S ) , 对于每个值维护一个单调队列表示可选的数,每次贪心取最大即可。 时间复杂度 O(S) O ( S )

#include <bits/stdc++.h>
using namespace std;

const int RLEN=1<<18|1;
inline char nc() {
    static char ibuf[RLEN],*ib,*ob;
    (ib==ob) && (ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
    return (ib==ob) ? -1 : *ib++;
}
inline int rd() {
    char ch=nc(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=nc();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=nc();}
    return i*f;
}
char obuf[RLEN],*wt=obuf;
inline void print(char c) {
    (wt==obuf+RLEN) && (fwrite(obuf,1,RLEN,stdout), wt=obuf);
    *wt++=c;
}
inline void W(int x) {
    static int buf[50];
    if(!x) {print('0'); return;}
    if(x<0) {print('-'); x=-x;}
    while(x) {buf[++buf[0]]=x%10; x/=10;}
    while(buf[0]) print(buf[buf[0]--]+'0');
}
const int N=1e6+50, B=5e5+25, INF=0x3f3f3f3f;
struct node {
    node *pre,*suf;
    int val;
    node():pre(NULL),suf(NULL){}
}Pool[N],*pool=Pool;
inline node* newnode(int v) {
    ++pool; pool->val=v;
    return pool;
}
struct List {
    node *bg,*ed;
    List():bg(NULL),ed(NULL){}
    inline bool empty() {return !bg;}
    inline int back() {return ed->val;}
    inline void pop_back() {
        node *t=ed;
        if(t==bg) {
            bg=ed=NULL;
        } else {
            ed=t->pre;
            t->pre->suf=NULL;
            t->pre=NULL;
        }
    }
    inline void push_back(int v) {
        node *t = newnode(v);
        if(!bg) {
            bg=ed=t; 
        } else {
            t->pre=ed;
            ed->suf=t;
            ed=t;
        }
        t->val=v;
    }
    inline int front() {return bg->val;}
    inline void pop_front() {
        node *t=bg;
        if(t==ed) {
            bg=ed=NULL;
        } else {
            bg=t->suf;
            t->suf->pre=NULL;
            t->suf=NULL;
        }
    }
};
List que[N];
int n,m,ans,c0,p[B],d[B],s[B],cnt[B];
inline int ceil(int x,int y) {
    return (x%y) ? (x/y+1) : (x/y);
}
inline void inc(int id,int pos,int pi) {
    while(!que[id].empty() && p[que[id].back()]>pi) que[id].pop_back();
    que[id].push_back(pos);
}
int main() {
    n=rd(), m=rd();
    for(int i=1;i<=n;i++) p[i]=rd(), d[i]=(rd()==1) ? 1 : -1;
    for(int i=n;i>=1;i--) s[i]=d[i]+s[i+1],c0+=(!s[i+1]);
    ans=s[1] ? (ceil(abs(s[1]),m)) : ((c0>=m) ? 0 : 1);
    if(m==1) {printf("%d\n",p[n]); return 0;}
    if(!ans) {
        for(int i=n;i;i--) cnt[i]=(!s[i+1])+cnt[i+1];
        int sze=0;
        for(int i=1;i<=n;i++) {
            if(s[i+1]) continue;
            while(!que[1].empty() && p[que[1].back()]>p[i] && cnt[i]+sze>m) que[1].pop_back(), --sze;
            que[1].push_back(i); ++sze;
        }
        while(!que[1].empty()) W(p[que[1].front()]), print(' '), que[1].pop_front();
    } else {    
        s[n+1]=INF;
        for(int i=1;i<=n-m+1;i++) inc(s[i+1]+B,i,p[i]);
        int sum=s[1], last=0;
        for(int i=1;i<m;i++) {
            int v=INF, id;
            for(int j=sum-ans+B;j<=sum+ans+B;++j) {
                while(!que[j].empty() && que[j].front()<last) que[j].pop_front();
                if(que[j].empty() || ceil(abs(j-B),m-i)>ans) continue;
                if(p[que[j].front()]<v) v=p[que[j].front()], id=j;
            }
            last=que[id].front();
            W(v); print(' ');
            sum=s[last+1];
            que[id].pop_front();
            if(i!=m-1) inc(s[n-m+i+2]+B,n-m+i+1,p[n-m+i+1]);
        }
        W(p[n]); print('\n');
    }
    fwrite(obuf,1,wt-obuf,stdout);
}   
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值