Dinner

2019.8.22

2021.5.29


Description

一个 \(n\) 个点的环,有点权。将环拆成至多 \(m\) 段,使得权值和最大的段最小。

Solution

之前写的博客是假做法,还是太年轻了,打算重写一遍。做过的题都想不起来了,幸好最后还是想到了正解。

考虑二分答案,关键在于 check。肯定还是要断环成链,但是发现断环之后相邻段不能快速转移,即知道了 \([l,r]\) 的答案,不能快速算出 \([l+1,r+1]\) 的答案。然后之后想了很多假做法……最后还是想到了,二分了最大段长度之后,一个点能扩展的最远位置是确定的,而且可以通过 two-pointer 快速求出来,具体来说就是记一下前缀和,因为时间均为正,所以前缀和是单调的,然后就可以快速得到后继。若从 \(u\) 开始转移,我们只需要判断转移 \(m\) 此后是不是节点编号大于等于 \(u+n\)

for(int tl=2;tl<=2*n;tl++){
    while(hd<=tl-n) to[hd]=hd+n; 
    while(hd<tl&&S[tl]-S[hd-1]>x) to[hd]=tl;
}

这样转移就会快一些,复杂度 \(O(nm\log \sum|T|)\) 。然后我们发现转移只会向后指,实际上形成了一个树形关系。转移 \(m\) 次实际上是求 \(m\) 级父亲,所以想到可以树上倍增。于是复杂度 \(O(n\log^2 n)\)

#include<stdio.h>
#include<algorithm>
using namespace std;

inline int read(){
    int x=0,flag=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    return flag? x:-x;
}

const int N=1e5+7;

struct E{
    int next,to;
}e[N];
int head[N],cnt=0;

inline void add(int id,int to){
    e[++cnt]=(E){head[id],to};
    head[id]=cnt;
}

int n,m,a[N],top=0,lim,mi,S[N],fa[N][12];

void dfs(int u){
    for(int i=1;i<12;i++)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    for(int i=head[u];i;i=e[i].next)
        fa[e[i].to][0]=u,dfs(e[i].to);
}

for(int tl=2;tl<=2*n;tl++){
    while(hd<=tl-n) to[hd]=hd+n; 
    while(hd<tl&&S[tl]-S[hd-1]>x) to[hd]=tl;
}

bool check(int x){
    int hd=1; cnt=0;
    for(int i=1;i<=2*n;i++) head[i]=0;
    for(int tl=2;tl<=2*n;tl++){
        while(hd<=tl-n){add(hd+n,hd);hd++;}
        while(hd<tl&&S[tl]-S[hd-1]>x){add(tl,hd);hd++;}
    }
    while(hd<2*n){add(2*n,hd);hd++;}
    fa[2*n][0]=2*n,dfs(2*n);
    for(int s=1;s<=n;s++){
        int u=s;
        for(int i=11;~i;i--)
            if(m&(1<<i)) u=fa[u][i];
        if(u>=s+n) return 1;
    }
    return 0;
}

int calc(){
    int lf=mi,rf=lim,ans=lim;
    while(lf<=rf){
        int mid=(lf+rf)>>1;
        if(check(mid)) rf=mid-1,ans=mid;
        else lf=mid+1;
    }
    return ans;
}

int main(){
    freopen("dinner.in","r",stdin);
    freopen("dinner.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        lim+=(a[i]=a[i+n]=read()),mi=max(a[i],mi);
    for(int i=1;i<=2*n;i++) S[i]=S[i-1]+a[i];
    printf("%d",calc());
}
posted @ 2019-08-22 18:58  Kreap  阅读(113)  评论(0编辑  收藏  举报