JZOJ 5521 Try to find out the wrong in the test

Try to find out the wrong in the test

Description

给出一个序列,第 i 个序列有两个关键值ci di
接下来要求把序列分成若干段,保证对于每个位置 i 所属的段的长度Leni,满足 ci <= Leni <= di ,求最多能分得的段数以及在段数最多的情况下分段的方案数。

Data Constraint

n <=106

Solution

考虑最简单的 dp
fi 1 ~i能够分成的最短段数, gi 为方案数。
枚举一个 j ,判断j+ 1 ~i是否是一个合法的段,若是,则 fi = max ( fi , fj + 1 ),同时维护gi即可。

假设不存在 ci 的下限限制,仅考虑 di 的上限限制。
若仅考虑上限限制,那么能够转移到 fi j 的取值范围必然是连续的一段且右端点为j- 1 ,于是我们设lefti表示仅考虑上限限制能够转移到 fi 的最小的 j

显然这个lefti有单调性,于是可以用个单调队列求解,具体实现可以参考标程。
那么能够转移到 fi j 即为lefti~ i -1中满足下限限制的 j

上限处理完了,接下来考虑比较麻烦的下限。
考虑分治。
设过程solve(l,r)表示计算出 flr glr
考虑如何完成 solve(l,r) ,找到 l +1~ r c最大的位置,记为 k ,先调用一遍solve(l,k1),接着考虑 fl fk1 fk fr 的影响,这样做的好处就是下限限制就固定为了 ck

ck C
首先只有在[max(l+C,k),r]内的 i 才能被转移。
接下来对能被转移的i进行分类讨论。

1、 lefti l i c +k- 1 i
可以发现,随着 i 的递增,能够转移到的位置只不过多了一个i- C +1,因而只需一开始用线段树查出一段区间的值,接着之后一个个 O(1) 更新即可。

接着分析复杂度,首先线段树查询一次 log n ,总共 O(n log n)
剩下的复杂度与扫过的位置有关。
首先我们能够知道满足条件的 i 一定小于等于min( k +C- 1 ,r)。
当最小满足条件的 i l+ C 时,我们令最大满足条件的i k +C- 1 一定不会更劣,此时扫过的区间长度是区间[l,k1]的长度。
当最小满足条件的 i k时,我们令最大满足条件的 i r一定不会更劣,此时扫过的区间长度是区间 [k,r] 的长度。
由于最小满足条件的 i 应是max(l+C,k),所以扫过的数的个数不超过两个区间长度的最小值。
既有 T(n)=T(x)+T(nx)+min(nx,x) ,理解成启发式合并,所以总的时间复杂度为 O (n log n)。

2、 lefti l i> k +C- 1
此时每一个i的合法转移区间都是 [l,k1] ,在线段树上查出,并用二分找到合法被转移区间的右边界,在线段树上打标记给合法区间进行更新。
复杂度显然为 O (n log n)。

3、 l < lefti< k
这样对于每个i分别求解,直接在线段树上进行区间查询。
可以发现对于每个 i 只会被至多进行一次线段树查询,故总复杂度为O(n log n)

4、 lefti k
不存在的,return吧。

当分治到区间[ l ,l]时,在线段树上查出 fl gl ,复杂度 O(n log n)
详细实现请见代码。

Code

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>

#define fo(i,j,l) for(int i=j;i<=l;++i)
#define fd(i,j,l) for(int i=j;i>=l;--i)

using namespace std;
typedef long long ll;
const ll N=11e5,M=4*N,mo=1000000007;

ll f[N],g[N],zd[M],sol[M],l1[M],l2[M],w[N];
int c[N],d[N],lef[N],bits[N],r[N][21],dd[N];
int n,len,le,ri,z1,b[N];
ll z2;

inline int max(int a,int b)
{return a>b?a:b;}

inline int min(int a,int b)
{return a<b?a:b;}

inline int read()
{
    int o=0; char ch=' ';
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar())o=o*10+ch-48;
    return o; 
}

int find(int le,int ri)
{
    int op=bits[ri-le+1]-1,k=1<<op;
    if(c[r[le][op]]>c[r[ri-k+1][op]])return r[le][op];else return r[ri-k+1][op];
}

inline void redo(int o)
{
    if(zd[o<<1]>zd[(o<<1)^1])zd[o]=zd[o<<1],sol[o]=sol[o<<1];
    else if(zd[o<<1]<zd[(o<<1)^1])zd[o]=zd[(o<<1)^1],sol[o]=sol[(o<<1)^1];
    else zd[o]=zd[o<<1],sol[o]=(sol[o<<1]+sol[(o<<1)^1])%mo;
}

inline void down(int o)
{
//  int ls=o<<1,rs=ls+1;
    if(l1[o]>zd[o<<1])zd[o<<1]=l1[o<<1]=l1[o],sol[o<<1]=l2[o<<1]=l2[o];
    else if(l1[o]==zd[o<<1])l1[o<<1]=l1[o],l2[o<<1]=(l2[o<<1]+l2[o])%mo,sol[o<<1]=(sol[o<<1]+l2[o])%mo;
    if(l1[o]>zd[(o<<1)^1])zd[(o<<1)^1]=l1[(o<<1)^1]=l1[o],sol[(o<<1)^1]=l2[(o<<1)^1]=l2[o];
    else if(l1[o]==zd[(o<<1)^1])l1[(o<<1)^1]=l1[o],l2[(o<<1)^1]=(l2[(o<<1)^1]+l2[o])%mo,sol[(o<<1)^1]=(sol[(o<<1)^1]+l2[o])%mo;
    l1[o]=-n-1,l2[o]=0;
}

void build(int o,int l,int r)
{
    zd[o]=-n-1;
    if(l==r)return;
    int mid=l+r>>1;
    build(o<<1,l,mid); build((o<<1)^1,mid+1,r);
}

void check(int o,int l,int r,int le,int ri)
{
    if(l==le&&r==ri){
        if(zd[o]>z1)z1=zd[o],z2=sol[o];
        else if(zd[o]==z1)z2=(z2+sol[o])%mo;
        return;
    }
    if(l1[o]>0)down(o);
    int mid=l+r>>1;
    if(ri<=mid)check(o<<1,l,mid,le,ri);
    else if(le>mid)check((o<<1)^1,mid+1,r,le,ri);
    else {
        check(o<<1,l,mid,le,mid);
        check((o<<1)^1,mid+1,r,mid+1,ri);
    }
    redo(o);
}

void xg(int o,int l,int r)
{
    if(r<le||l>ri)return;
    if(l>=le&&r<=ri){
        if(z1>zd[o])zd[o]=l1[o]=z1,sol[o]=l2[o]=z2;
        else if(z1==zd[o])sol[o]=(sol[o]+z2)%mo,l1[o]=z1,l2[o]=(l2[o]+z2)%mo;
        return;
    }
    if(l1[o]>0)down(o);
    int mid=l+r>>1;
    if(ri>=l&&le<=mid)xg(o<<1,l,mid);
    if(ri>mid&&le<=r)xg((o<<1)^1,mid+1,r);
    redo(o);
}

void move(int o,int l,int r,int posi)
{
    if(l==r){
        if(f[l]>zd[o])zd[o]=f[l],sol[o]=g[l];
        else if(f[l]==zd[o])sol[o]=(sol[o]+g[l])%mo;
        f[l]=zd[o]; g[l]=sol[o];
        return;
    }
    if(l1[o]>0)down(o);
    int mid=l+r>>1;
    if(posi<=mid)move(o<<1,l,mid,posi);else move((o<<1)^1,mid+1,r,posi);
    redo(o);
}

void solve(int l,int r)
{
    if(l==r){
        move(1,1,n,l);
        return;
    }
    int k=find(l+1,r),cc=c[k];
    solve(l,k-1);
    int ty=max(k,l+cc);
    if(ty>r){
        solve(k,r);
        return;
    }
    if(lef[ty]<=l){
        z1=-n-1,z2=0; int bj=min(k+cc,r+1);
        check(1,1,n,l,ty-cc);
        ++z1;
        if(z1>f[ty])f[ty]=z1,g[ty]=z2;
        else if(z1==f[ty])g[ty]=(g[ty]+z2)%mo;
        for(++ty;lef[ty]<=l&&ty<bj;++ty){
            int j=ty-cc;
            if(f[j]+1>z1)z1=f[j]+1,z2=g[j];
            else if(f[j]+1==z1)z2=(z2+g[j])%mo;
            if(z1>f[ty])f[ty]=z1,g[ty]=z2;
            else if(z1==f[ty])g[ty]=(g[ty]+z2)%mo; 
        }
        if(ty>=bj&&ty<=r&&lef[ty]<=l){
            int zb=ty,yb=r+1;
            while(zb+1<yb){
                int mid=zb+yb>>1;
                if(lef[mid]<=l)zb=mid;else yb=mid;
            }
            le=ty; ri=zb;
            xg(1,1,n);
            ty=ri+1;
        }
    }
    for(int i=ty;i<=r&&lef[i]<k;++i){
        if(lef[i]>i-cc)continue;
        z1=-n-1,z2=0;
        check(1,1,n,lef[i],min(k-1,i-cc));
        ++z1;
        if(z1>f[i])f[i]=z1,g[i]=z2;
        else if(z1==f[i])g[i]=(g[i]+z2)%mo;
    }
    solve(k,r);
}

int main()
{
    cin>>n;
    build(1,1,n);
    fo(i,1,n)c[i]=read(),d[i]=read();
    fo(i,1,n)bits[i]=bits[i>>1]+1;
    len=bits[n]-1;
    fo(i,1,n)r[i][0]=i;
    int u=1,v;
    fo(l,1,len){
        v=u,u<<=1;
        fo(i,1,n-u+1)
        if(c[r[i][l-1]]>c[r[i+v][l-1]])r[i][l]=r[i][l-1];else r[i][l]=r[i+v][l-1];
    }
    lef[0]=0; d[0]=n+1;
    int le=1,ri=1; b[1]=0;
    fo(i,1,n){
        lef[i]=lef[i-1]; 
        while(ri>=le&&d[i]<d[b[ri]])--ri;
        b[++ri]=i;
        while(lef[i]+d[b[le]]<i){
            ++lef[i];
            if(lef[i]>=b[le])++le;
        }
    }
    int zd=0;
    fo(i,1,n)f[i]=-n-1;
    fo(i,1,n)if(lef[i]==0){
        zd=max(zd,c[i]);
        if(i>=zd)f[i]=g[i]=1;
    }else break;
    solve(1,n);
    if(f[n]<=0){
        puts("-1");
        return 0;
    }
    printf("%d ",f[n]);
    printf("%lld",g[n]);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值