[bzoj3711]Druzyny

本文介绍了一种使用动态规划和分治策略解决特定分组优化问题的方法。该问题要求在满足个体成员对组大小限制的前提下,最大化组的数量,并计算达到最优解的不同方案数量。文章详细阐述了算法的设计思路,包括如何利用数据结构进行高效状态转移。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

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

神题

我们考虑DP。
f[i]表示将前i个人分组的最大值,-1表示不可能,g[i]来表示最大化分组情况下的方案数。
因为g的转移和f类似,我们接下来只考虑f。
首先观察d的限制,可以通过预处理left[i]表示所有left[i]<=j<i均满足j+1~i的d最小值不比i-j+1小。那么只有这些j有可能可以转移到i。
这个left显然有单调性,于是预处理left的时候,只需要一个单调队列来资瓷尾加头删的最小值维护即可。
接下来我们来考虑c,发现并不是很好考虑?
于是我们来考虑分治,根据c来分治。
这样可以在每一次转移明确c的最大值。
具体的,我们考虑现在确定[l,r]的f值,记为过程solve(l,r)。
我们先找到[l+1,r]中c的最大值所在位置k。
然后我们考虑用[l,k-1]的状态转移到[k,r]。
[l,k-1]的状态可以先通过solve(l,k-1)确定。
这个转移完成后可以继续调用solve(k,r)。
于是我们来解决这个转移。
我们来考虑[max(k,l+c),min(r,k-1+c)]的i。
显然只有这个区间的i能被[l,k-1]转移到。
分类讨论:
1、left[i]<l且i<=k-1+c。
满足条件的i是连在一起的,而且i+1相对于i来说,只不过多了一个i+1-c。
因此对于第一个i用线段树查询后,接下来的都可以O(1)更新。
2、left[i]<l且i>k-1+c。
这个时候每个i的合法范围都是[l,k-1]。
二分出满足条件的i的区间[ll,rr]。
用线段树查询出[l,k-1]的答案,然后打标记给[ll,rr]。
3、l<=left[i]<k
这个时候因为left[i]不确定,对于这样的每个i我们只能分别求解,在线段树对应区间求出答案。
4、left[i]>=k。
那就没有[l,k-1]的可以转移啦,退出吧!
接下来分析一下复杂度。
先看1,首先要一个log n,接下来与扫过的i个数有关。
假设满足条件的最小i是k,最大i是k-1+c或r,我们直接当做最大i是r一定不比真实最大i小。
此时扫过的i个数是[k,r]的区间长度。
假设满足条件的最小i是l+c,最大i是k-1+c或r,我们直接当做最大i是k-1+c一定不比真实最大i小。
此时扫过的i个数是[l,k-1]的区间长度。
那么到底是多少呢?我们发现最小i是k与l+c取max,那么扫过的i个数应当是两者的较小值。
所以我们得到式子T(n)=T(x)+T(n-x)+log n+min(x,n-x)。
考虑到log n的影响只有n个,这部分是n log n的。
后面那个可以理解为启发式合并,于是T(n)=n log n。
接下来看2,显然每次只需要一个log n,分治结构有n个部分,那么就是n log n。
接下来看3,对于每个i均需要一个log n,但由于每个i显然只会被这样搞一次(分治结构中不同的部分要么left取值范围不相交要么被搞的i范围不相交),所以也是n log n。
然后这题就是n log n的!
之后当分治到l=r时,就可以在线段树中查出f和g。
实现详见代码。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=1000000+10,mo=1000000007;
struct dong{
    int c,f,g,ff,gg;
} tree[maxn*4];
int f[maxn],g[maxn],c[maxn],d[maxn],dl[maxn],left[maxn];
int i,j,k,l,t,n,m,head,tail;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void cmp(int &f,int &g,int ff,int gg){
    if (ff>f) f=ff,g=gg;
    else if (f==ff) g=(g+gg)%mo;
}
void mark(int p,int ff,int gg){
    cmp(tree[p].f,tree[p].g,ff,gg);
    cmp(tree[p].ff,tree[p].gg,ff,gg);
}
void down(int p){
    if (tree[p].ff>-1){
        mark(p*2,tree[p].ff,tree[p].gg);
        mark(p*2+1,tree[p].ff,tree[p].gg);
        tree[p].ff=-1;tree[p].gg=0;
    }
}
void update(int p){
    int j=tree[p*2].c,k=tree[p*2+1].c;
    if (c[j]>c[k]) tree[p].c=j;else tree[p].c=k;
    j=tree[p*2].f;k=tree[p*2+1].f;
    if (j>=k){
        tree[p].f=j;tree[p].g=tree[p*2].g;
        if (j==k) (tree[p].g+=tree[p*2+1].g)%=mo;
    }
    else{
        tree[p].f=k;tree[p].g=tree[p*2+1].g;
    }
}
void build(int p,int l,int r){
    tree[p].ff=-1;tree[p].gg=0;
    if (l==r){
        /*if (l==0) tree[p].f=0,tree[p].g=1;
        else */tree[p].c=l,tree[p].f=-1,tree[p].g=0;
        return;
    }
    int mid=(l+r)/2;
    build(p*2,l,mid);build(p*2+1,mid+1,r);
    update(p);
}
int find(int p,int l,int r,int a,int b){
    if (l==a&&r==b) return tree[p].c;
    down(p);
    int mid=(l+r)/2;
    if (b<=mid) return find(p*2,l,mid,a,b);
    else if (a>mid) return find(p*2+1,mid+1,r,a,b);
    else{
        int j=find(p*2,l,mid,a,mid),k=find(p*2+1,mid+1,r,mid+1,b);
        if (c[j]>c[k]) return j;else return k;
    }
}
void change(int p,int l,int r,int a,int b,int ff,int gg){
    if (l==a&&r==b){
        mark(p,ff,gg);
        return;
    }
    down(p);
    int mid=(l+r)/2;
    if (b<=mid) change(p*2,l,mid,a,b,ff,gg);
    else if (a>mid) change(p*2+1,mid+1,r,a,b,ff,gg);
    else change(p*2,l,mid,a,mid,ff,gg),change(p*2+1,mid+1,r,mid+1,b,ff,gg);
    update(p);
}
void query(int p,int l,int r,int a,int b,int &f,int &g){
    if (a>b){
        f=-1;
        g=0;
        return;
    }
    if (l==a&&r==b){
        f=tree[p].f;
        g=tree[p].g;
        return;
    }
    down(p);
    int mid=(l+r)/2;
    if (b<=mid) query(p*2,l,mid,a,b,f,g);
    else if (a>mid) query(p*2+1,mid+1,r,a,b,f,g);
    else{
        int j,k;
        query(p*2,l,mid,a,mid,j,k);
        query(p*2+1,mid+1,r,mid+1,b,f,g);
        if (j>f) f=j,g=k;
        else if (j==f) g=(g+k)%mo;
    }
}
int get(){
    while (head<=tail&&dl[head]<j+1) head++;
    return d[dl[head]];
}
void prepare(){
    head=1;tail=0;
    j=0;
    fo(i,1,n){
        while (head<=tail&&d[dl[tail]]>=d[i]) tail--;
        dl[++tail]=i;
        while (i-get()>j) j++;
        left[i]=j;
    }
}
int binary(int l,int r,int x){
    int mid;
    while (l<r){
        mid=(l+r+1)/2;
        if (left[mid]<x) l=mid;else r=mid-1;
    }
    return l;
}
void trans(int l,int r,int k,int c){
    int st=max(k,l+c),ed=min(r,k-1+c),ff=-1,gg=0,i=st;
    while (i<=ed){
        if (left[i]>=k) return;
        if (left[i]>=l) break;
        if (i==st) query(1,0,n,l,i-c,ff,gg);
        else cmp(ff,gg,f[i-c],g[i-c]);
        if (ff!=-1) cmp(f[i],g[i],ff+1,gg);
        i++;
    }
    if (i>r) return;
    if (left[i]<l){
        int j=binary(i,r,l);
        query(1,0,n,l,k-1,ff,gg);
        if (ff!=-1) change(1,0,n,i,j,ff+1,gg);
        i=j+1;
    }
    while (i<=r){
        if (left[i]>=k) return;
        query(1,0,n,left[i],min(k-1,i-c),ff,gg);
        if (ff!=-1) cmp(f[i],g[i],ff+1,gg);
        i++;
    }
}
void solve(int l,int r){
    if (l>r) return;
    if (l==r){
        change(1,0,n,l,l,f[l],g[l]);
        query(1,0,n,l,l,f[l],g[l]);
        return;
    }
    int k=find(1,0,n,l+1,r);
    solve(l,k-1);
    trans(l,r,k,c[k]);
    solve(k,r);
}
int main(){
    //freopen("data.in","r",stdin);
    n=read();
    fo(i,1,n) c[i]=read(),d[i]=read();
    build(1,0,n);
    fo(i,1,n) f[i]=-1,g[i]=0;
    g[0]=1; 
    prepare();
    solve(0,n);
    if (f[n]<=0) printf("NIE\n");else printf("%d %d\n",f[n],g[n]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值