题目描述
体育课上,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]);
}