[bzoj3711]Druzyny

来源:互联网 发布:大数据产业规模 编辑:程序博客网 时间:2024/06/15 05:03

题目描述

体育课上,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]);}
原创粉丝点击