HNOI2013旅行

 一道欺负我智商的题。。。

本来想打单调队列优化dp的,结果看到算法标签就点了此题

 洛谷题面

 

首先你要理解题意,蒟蒻理解了好久。它就是说,给你一个由1和-1组成的数列,让你分成m段,并让这m段区间和最大值最小,还要求多种方案时字典序最小。

我也不知道大佬怎么做的,反正我不会高斯消元。。。

哦,对了,如果输入的是0,表示他不喜欢则那一位为-1。

设总和为S。区间和最小值为ans。后缀和为sum[],后缀中0的个数为cnt[]。

为什么是后缀,往后看。。。

首先考虑特殊情况:

  • 全是1 显然答案为ans=ceil(S/m);ceil()是向上取整。
  • 全是-1 ans=ceil(abs(S)/m)
  • 一半全1,一半全-1 比如11111-1-1-1可以变成11(111-1-1-1),括号里为0,可以与任意区间搭配,于是变成了上面的情况。


所以ans=ceil(abs(S)/m),简易证明:你可以用第三中方法狂消1和-1直到只有一种数,剩下来的数的个数是abs(S)。

如果abs(S)=0且能分的区间不足m那就另当别论。。。

由于我太菜了,还有情况没考虑就多多包含

先预处理sum[],ans,cnt[];

  •  S=0
  • cnt[1]>=m,此时找sum[i]=0的点i,用单调队列维护找出字典序最小的一条即可。
  • cnt[1]<m,ans>0,和下面一起处理。
  • S!=0 sum的每一种取值分开考虑。


  设上一个选的为last,则这一个i能选要满足abs(sum[last+1]-sum[i+1])<=ans,那么我们枚举sum[i+1]的取值时就可以直接从sum[last+1]-ans到sum[last+1]+ans。
  并且abs(sum[i+1]/m'(即剩下要选的数量))要满足小于等于ans,i还有后面的数不能超过m'个。
  然后跑单调队列就完啦,不要告诉我你切黑题还不会这个。。。

实现起来还有不少细节,比如负数下标之类的,仔细看下应该都能懂

如果你想TLE的话deque走起

#include<cstdio>
#include<algorithm>
const int N=5e5+5;
int a[N],sum[N],cnt[N],tot;
struct node
{
    int l,r,val;
}p[N<<1];
inline int newnode(int l,int r,int val)
{
    p[++tot]=(node){l,r,val};
    return tot;
}
struct que
{
    int start,end,len;
    inline void push_back(int x)
        {
            if(!len)start=end=newnode(0,0,x);
            else p[end].r=newnode(end,0,x),end=p[end].r;
            ++len;
        }
    inline int empty(){return !len;}
    inline int front(){return p[start].val;}
    inline int back(){return p[end].val;}
    inline void pop_front(){start=p[start].r;--len;}
    inline void pop_back(){end=p[end].l;--len;}
    inline void push(int x)
        {
            while(!empty()&&a[back()]>a[x])pop_back();
            push_back(x);
        }
}dui[N<<1],dui1[N<<1],*q=dui+N,*q1=dui1+N;
inline int min(const int &x,const int &y)
{return a[x]<a[y]?x:y;}
int main()
{
    int n,m,ans;scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)scanf("%d%d",&a[i],&sum[i]),sum[i]=sum[i]?1:-1;
    for(int i=n-1;i;--i)sum[i]+=sum[i+1];
    for(int i=n;i;--i){cnt[i]+=cnt[i+1];if(!sum[i])++cnt[i];}
    cnt[n+1]=-1;
//    for(int i=1;i<=n+1;++i)printf("%d %d\n",sum[i],cnt[i]);
    int s=sum[1];
    ans=s?(abs(s)-1)/m+1:cnt[1]<m;//printf("ss%d\n",ans);
    if(ans)
    {
        a[n+1]=n+1;int la=0;
        for(int i=2;i<=n;++i)
            q1[sum[i]].push_back(i-1);
        for(int i=1;i<m;++i)
        {
            int aa=n+1;
            for(int j=sum[la+1]-ans;j<=sum[la+1]+ans;++j)
            {
                if((abs(j)+m-i-1)/(m-i)>ans)continue;
                while(!q1[j].empty()&&n-q1[j].front()>=m-i){if(q1[j].front()>la)q[j].push(q1[j].front());q1[j].pop_front();}
                while(!q[j].empty()&&q[j].front()<=la){q[j].pop_front();}
                if(!q[j].empty())aa=min(aa,q[j].front());
            }
            la=aa;
            printf("%d ",a[aa]);
        }
    }
    else
    {
        for(int i=1,j=2;i<m;++i)
        {
            for(;cnt[j+1]>=m-i;++j)
                if(!sum[j+1])
                    q[0].push(j);
            printf("%d ",a[q[0].front()]);
            q[0].pop_front();
        }
    }
    printf("%d\n",a[n]);
    return 0;
}

转载于:https://www.cnblogs.com/cx233666/p/8708482.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值