BZOJ3711: [PA2014]Druzyny(分治)

传送门

题意:

体育课上,n个小朋友排成一行(从1到n编号),老师想把他们分成若干组,每一组都包含编号连续的一段小朋友,每个小朋友属于且仅属于一个组。
第i个小朋友希望它所在的组的人数不多于d[i],不少于c[i],否则他就会不满意。
在所有小朋友都满意的前提下,求可以分成的组的数目的最大值,以及有多少种分组方案能达到最大值。

题解:
模拟赛上遇到的。。这道题思路不错。

Si i 的合法决策集合。
mx[x]表示到 x 的合法最优方案的组数。
dp[x]表示到 x 的最优方案下的方案数。
可以想到朴素的DP转移:
mx[i]=maxjSi{mx[j]}+1
dp[x]=jS,mx[j]=max{mx[k]},kSdp[j]

发现带着 S 的限制想要优化这个DP是很难的,这时候通过分治转化限制条件是一种很好的思路。
考虑 jSi 集合需要满足的条件:

max{cj,cj+1,...,ci}ijmin{dj,dj+1,...,di}

发现如果只考虑 d 的限制,那么i的合法集合为 [left[i],i] ,且 left[i] 单调不降。

当然如果加上 c 的限制,这个区间内的一些地方就被删除而成为了零散的几部分,然后分治的思路就是,我们枚举当前最大c的位置,那么右边区间所有到左边的决策都会以该分界点作为最大值,这时候限制就很明了了。

但是还有一个问题是,如果单纯的这样递归下去,复杂度

T(n)=T(x)+T(nx)+nT(n)=n2

并没有实质性的优化,这时候要考虑这个特殊的限制 c 要怎么用上,若当前分治中心为mid,可以明确的是决策区间随着右边枚举状态 dp[i] 而往右挪,这时候更新状态并不需要 O(n) ,考虑怎么优化这个 DP :

1.找出第一个可行位置 p (使得中心点满足条件)。
2.初始决策区间的l max{left[p],l} r min{mid1,pc[mid]}
3.假设左指针一直处于左区间开头,右指针每次暴力挪动1位,那么更新状态为 O(1) 。挪到左区间末尾停止,二分可以更新的状态的区域并在线段树上更新,时间复杂度 O(logn)
4.暴力查找 left[i] 在区间中的位置,每个 i 只会查询一次,总体复杂度为O(nlogn)

比较有疑问的是第3个暴力为什么是 nlogn 而不是 n2 ,这是因为每次暴力挪动右指针,次数不会超过左区间的长度,而枚举 i 不会超过右区间长度,所以这部分的复杂度为

T(n)=T(x)+T(nx)+min{x,nx}

类似启发式合并的复杂度分析,总时间为 O(nlogn)

(哦对了这道题卡空间)

#include<bits/stdc++.h>
using namespace std;
const int R_LEN=(1<<18)|1;
char ibuf[R_LEN],*sb,*tb;
inline char getc(){
    (sb==tb) && (tb=(sb=ibuf)+fread(ibuf,1,R_LEN,stdin));
    return (sb==tb) ? -1: *sb++;
}
inline int rd(){
    char ch=getc(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=getc();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=getc();}
    return i*f;
} 

const int Mod=1e9+7;
const int N=1e6+50,M=1e5;
const int INF=0x3f3f3f3f;
int n,c[N],d[N],lft[N];
int mnd[N*2+M];
inline void build_d(int k,int l,int r){
    if(l==r){mnd[k]=d[l]; return;}
    int mid=(l+r)>>1;
    build_d(k<<1,l,mid); build_d(k<<1|1,mid+1,r);
    mnd[k]=min(mnd[k<<1],mnd[k<<1|1]);
}
inline int query_d(int k,int l,int r,int L,int R){
    if(L<=l&&r<=R) return mnd[k];
    int mid=(l+r)>>1;
    if(R<=mid) return query_d(k<<1,l,mid,L,R);
    else if(L>mid) return query_d(k<<1|1,mid+1,r,L,R);
    else return min( query_d(k<<1,l,mid,L,R), query_d(k<<1|1,mid+1,r,L,R));
}
int mxc[N*2+M],pos_c[N*2+M];
inline void build_c(int k,int l,int r){
    if(l==r) {
        mxc[k]=c[l]; pos_c[k]=l; return;
    }int mid=(l+r)>>1;
    build_c(k<<1,l,mid); build_c(k<<1|1,mid+1,r);
    (mxc[k<<1]>=mxc[k<<1|1])? (mxc[k]=mxc[k<<1],pos_c[k]=pos_c[k<<1]) :(mxc[k]=mxc[k<<1|1],pos_c[k]=pos_c[k<<1|1]);
}
typedef pair<int,int> pii;
inline pii querymx(int k,int l,int r,int L,int R){
    if(L<=l&&r<=R) return make_pair(mxc[k],pos_c[k]);
    int mid=(l+r)>>1;
    if(R<=mid) return querymx(k<<1,l,mid,L,R);
    else if(L>mid) return querymx(k<<1|1,mid+1,r,L,R);
    else{
        pii tr1=querymx(k<<1,l,mid,L,R),tr2=querymx(k<<1|1,mid+1,r,L,R);
        return (tr1.first<tr2.first)?tr2:tr1; 
    } 
}
struct data{
    int mx,val;
    data(){}
    data(int mx,int val):mx(mx),val(val){}
    friend inline data operator +(const data &a,const data &b){
        if(a.mx!=b.mx) return (a.mx>b.mx) ?a:b;
        return data(a.mx,(a.val+b.val)%Mod);
    }
};
data mxf[N*2+M],f[N],tag[N*2+M];
inline void build_f(int k,int l,int r){
    if(l==r) {
        mxf[k]=f[l]; tag[k]=data(-INF,0); return;
    }int mid=(l+r)>>1;
    build_f(k<<1,l,mid); build_f(k<<1|1,mid+1,r);
    mxf[k]=(mxf[k<<1]+mxf[k<<1|1]);
    tag[k]=(tag[k<<1]+tag[k<<1|1]);
}
inline void modify_cov(int k,int l,int r,int pos,data val){
    if(l==r) {
        mxf[k]=val; return;
    }int mid=(l+r)>>1;
    (pos<=mid)? (modify_cov(k<<1,l,mid,pos,val)): (modify_cov(k<<1|1,mid+1,r,pos,val));
    mxf[k]=(mxf[k<<1]+mxf[k<<1|1]);
}
inline void modify_add(int k,int l,int r,int L,int R,data val){
    if(L<=l&&r<=R){
        tag[k]=tag[k]+val;
        return;
    }int mid=(l+r)>>1;
    if(R<=mid) modify_add(k<<1,l,mid,L,R,val);
    else if(L>mid) modify_add(k<<1|1,mid+1,r,L,R,val);
    else modify_add(k<<1,l,mid,L,R,val),modify_add(k<<1|1,mid+1,r,L,R,val); 
}
inline data askc(int k,int l,int r,int pos){
    data tp(-INF,0);
    while(1){
        tp=tp+tag[k];
        if(l==r)break;
        int mid=(l+r)>>1;
        (pos<=mid) ?(k=k<<1,r=mid):(k=(k<<1)|1,l=mid+1);
    }
    return tp;
}
inline data qryc(int k,int l,int r,int L,int R){
    if(L>R) return data(-INF,0);
    if(L<=l&&r<=R) return mxf[k];
    int mid=(l+r)>>1;
    if(R<=mid) return qryc(k<<1,l,mid,L,R);
    else if(L>mid) return qryc(k<<1|1,mid+1,r,L,R);
    else return  qryc(k<<1,l,mid,L,R)+qryc(k<<1|1,mid+1,r,L,R);  
}
inline int srh(int l,int r,int lim){
    int ans;
    while(l<=r){
        int mid=(l+r)>>1;
        if(lft[mid]<=lim) ans=mid,l=mid+1;
        else r=mid-1;
    } 
    return ans;
} 
inline data upt(const data &t) {return data(t.mx+1,t.val);}
inline void upt(int l,int mid,int r){
    int p=max(mid,c[mid]+l),nowl=max(l,lft[p]),nowr=min(mid-1,p-c[mid]);
    if(p>r||nowl>=mid) return;
    data tp=qryc(1,0,n,nowl,nowr); 
    while(p<=r&&nowl<=l){
        if(nowr>=mid-1){
            int R=srh(p,r,l);
            modify_add(1,0,n,p,R,upt(tp));
            p=R+1; break;
        }
        f[p]=f[p]+upt(tp); tp=tp+(f[++nowr]); 
        nowl=max(nowl,lft[++p]);
    }
    for(;p<=r;){
        nowl=lft[p]; nowr=min(p-c[mid],mid-1);
        if(nowl>mid) return;
        f[p]=f[p]+upt(qryc(1,0,n,nowl,nowr));
        ++p;
    }
}
inline void solve(int l,int r){
    if(l>r) return;
    if(l==r){
        modify_cov(1,0,n,l,f[l]=(f[l]+askc(1,0,n,l)));
        return;
    }int mid=querymx(1,0,n,l+1,r).second;
    solve(l,mid-1);
    upt(l,mid,r);
    solve(mid,r);
}
int main(){
    n=rd();
    for(int i=1; i<=n; i++) c[i]=rd(),d[i]=rd();
    build_d(1,1,n); build_c(1,0,n);
    for(int i=1;i<=n;i++){
        lft[i]=lft[i-1];
        while(query_d(1,1,n,lft[i]+1,i)<i-lft[i]) 
            ++lft[i];
    }
    for(int i=1;i<=n;i++) f[i].mx=-INF;
    f[0].val=1;  build_f(1,0,n);
    solve(0,n); 
    (f[n].mx>0)? printf("%d %d\n",f[n].mx,f[n].val): puts("NIE");
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值