首页 > 其他 > 详细

[bzoj3141] [HNOI2013]旅行

时间:2019-01-11 11:01:01      阅读:164      评论:0      收藏:0      [点我收藏+]

Description

技术分享图片

Input

第一行为两个空格隔开的正整数n, m,表示旅行的城市数与旅行所花的月数。接下来n行,其中第 i行包含两个空格隔开的整数Ai和Bi,Ai表示他第i个去的城市编号。Bi为0或1;如果Bi=0则表示城市Ai没有小L想去的景点,如果Bi=1则表示城市Ai有小L想去的景点,
Ai两两不同且有1<=Ai<=N,即{Ai}为1,2....N的一个排列。
例如{2,1,3,4...N}
N<=500000,M<=200000

Output

t仅包括一行,包含m个空格隔开的正整数X1,X2...Xm,t仅包括一行,包含m个空格隔开的正整数X1,X2...Xm,为给小L安排的旅行计划对应的路线。为给小L安排的旅行计划对应的路线。

Sample Input

8 3
2 0
3 1
4 1
1 0
5 0
6 1
7 1
8 0

Sample Output

1 6 8

Solution

由于要计算\(0\)的个数和\(1\)的个数,一个很显然的想法就是把\(0\)改成\(-1\)

先不管字典序,考虑下答案应该是多少。

显然有这么两条性质:

  • 若原数列只由\(1\)\(-1\)构成,答案显然就是\(\lceil\frac{n}{k}\rceil\)
  • 否则,必定存在一段和为\(0\)的子数列,这段其实没有意义,随便和左边或右边合并都行。

所以可以把\(-1\)\(1\)消掉一种,然后就变成了第一种情况。

答案就是:
\[ \lceil\frac{|sum[1]|}{k}\rceil? \]
其中\(sum[x]\)为数列的后缀和。

当然有一种特殊情况:上面的式子为\(0\),但是没有\(k\)\(sum[x]=0\)的点,显然凑不出\(k\)个和为\(0\)的段,所以这种情况答案是\(1\)

然后考虑字典序怎么做:

如果答案为\(0\),单调队列随便搞搞就行了。

否则的话,设上次选的位置为\(l\),那么这次选的数的后缀和\(s\)显然要满足\(|sum[l]-s|\leqslant ans\),即\(sum[l]-ans\leqslant s \leqslant ans+sum[l]\)

所以对于\(sum[x]\)相同的,把他们归为一类处理,然后用单调队列维护就行了。

注意选完这个数之后,后面形成了一个子问题,答案要不大于整个问题的答案,设当前选的数位置为\(x\),即:
\[ \lceil\frac{|sum[x+1]|}{n-x}\rceil\leqslant ans \]
注意负数下标特别处理,具体看代码。

#include<bits/stdc++.h>
using namespace std;
 
void read(int &x) {
    x=0;int f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}
 
void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');}

const int maxn = 1e6+10;

struct data {int l,r,val;}w[maxn<<2];

int tot;

int n,k,id[maxn],t[maxn],sum[maxn],cnt[maxn];

struct Priority_Queue {
    int st,ed,len;
    int empty() {return !len;}
    void push_back(int x) {
        if(len) w[++tot]=(data){ed,0,x},w[ed].r=tot,ed=w[ed].r;
        else w[++tot]=(data){0,0,x},st=ed=tot;
        len++;
    }
    void pop_back() {ed=w[ed].l,len--;}
    void pop_front() {st=w[st].r,len--;}
    int back() {return w[ed].val;}
    int front() {return w[st].val;}
    void push(int x) {
        while(!empty()&&id[w[ed].val]>id[x]) pop_back();
        push_back(x);
    }
}alpha[maxn<<1],beta[maxn<<2],*q1=alpha+maxn,*q=beta+maxn;  //负数下标处理

int main() {
    read(n),read(k);
    for(int i=1;i<=n;i++) {read(id[i]),read(t[i]);if(!t[i]) t[i]--;}
    for(int i=n;i;i--) sum[i]+=sum[i+1]+t[i];
    for(int i=n;i;i--) cnt[i]=cnt[i+1]+(sum[i]==0);
    cnt[n+1]=-1;
    int ans=sum[1]?(abs(sum[1])+k-1)/k:(cnt[1]<k);
    if(!ans) {
        for(int i=1,j=2;i<k;i++) {
            for(;cnt[j+1]>=k-i;j++) if(!sum[j+1]) q[0].push(j);
            printf("%d ",id[q[0].front()]);q[0].pop_front();
        }
    } else {
        int l=0;id[n+1]=n+1;
        for(int i=2;i<=n;i++) q[sum[i]].push_back(i-1);
        for(int i=1;i<k;i++) {
            int s=n+1;
            for(int j=sum[l+1]-ans;j<=sum[l+1]+ans;j++) {
                if((abs(j)+k-i-1)/(k-i)>ans) continue;
                while(!q[j].empty()&&n-q[j].front()>=k-i) {
                    if(q[j].front()>l) q1[j].push(q[j].front());
                    q[j].pop_front();
                } 
                while(!q1[j].empty()&&q1[j].front()<=l) q1[j].pop_front();
                if(!q1[j].empty()) if(id[s]>id[q1[j].front()]) s=q1[j].front();
            }
            l=s;printf("%d ",id[s]);
        }
    }printf("%d\n",id[n]);
    return 0;
}

[bzoj3141] [HNOI2013]旅行

原文:https://www.cnblogs.com/hbyer/p/10253761.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!