[bzoj3711][PA2014]Druzyny【分治】【dp】
[题目描述]
3711: [PA2014]Druzyny
Time Limit: 40 Sec Memory Limit: 128 MBSubmit: 343 Solved: 74
[Submit][Status][Discuss]
Description
体育课上,n个小朋友排成一行(从1到n编号),老师想把他们分成若干组,每一组都包含编号连续的一段小朋友,每个小朋友属于且仅属于一个组。
第i个小朋友希望它所在的组的人数不多于d[i],不少于c[i],否则他就会不满意。
在所有小朋友都满意的前提下,求可以分成的组的数目的最大值,以及有多少种分组方案能达到最大值。
Input
第一行一个整数n(1<=n<=1000000),表示小朋友的数目。
接下来n行,每行两个整数c[i],d[i](1<=c[i]<=d[i]<=n),表示i所在组的人数的最小值和最大值。
Output
如果不存在这样的方案,仅输出一行NIE。
否则输出一行包含两个整数,组的数目的最大值、方案数量。(方案数量对1000000007取模)
Sample Input
9
1 4
2 5
3 4
1 5
1 1
2 5
3 5
1 3
1 1
样例输入2:
2
1 1
2 2
Sample Output
5 2
样例输出2:
NIE
HINT
Source
[题解]记f[i]表示选择已经选取1..i,且i为右端点的答案,暴力转移是O(n^2)
不难发现,如果只考虑d[i],那么i作为右端点,可行的左端点的选择区间是单调增的,记为g[i],可以用线段树在O(n log n)求出。
考虑分治,每次选取当前区间[l..r]内c[i]最大的作为分治中点mid。
考虑[l..mid]对[mid..r] 的贡献
那么若只有c[i]的限制,[mid..r]中每一个i可行的转移区间的的左端点都是l,右端点每次只会+1,i变化时可以O(1)更新答案
考虑d[i]的限制,每次g[i]>当前左端点时,用线段树更新答案。由于每个g[i]只会对之前的一个分治区间产生影响。所以左端点的右移的复杂度的和是O(n log n)的。
为了保证分治的复杂度,左边对右边的影响要在min(左区间大小,右区间大小)处理出来,当L>R时复杂度显然是O(R)的,当L<R时,为了在O(L)的复杂度内得出答案。当右区间访问到mid+L时,左区间可行的右端点显然是mid,此时二分出左区间中还包含哪些g[i],在线段树上打标记更新答案。
具体见代码:
/************************************************************** Problem: 3711 User: duweihua Language: C++ Result: Accepted Time:26552 ms Memory:129412 kb ****************************************************************/ # include <bits/stdc++.h> # define N 1000010 # define inf 1e9 # define P 1000000007 using namespace std; struct node{ int pl,pr,mn,mx,mxid,ans,ansnum,l,r,tans,tnum; }T[N*2+100]; int c[N],d[N],g[N],f[N],num[N],place,rt,nowans,nownum,n,ti,cnt; int read(){ int tmp=0,fh=1; char ch=getchar(); while (ch<'0'||ch>'9'){if (ch=='-') fh=-1; ch=getchar();} while (ch>='0'&&ch<='9'){tmp=tmp*10+ch-'0'; ch=getchar();} return tmp*fh; } void change(int x){ T[x].tnum=0; T[x].tans=max(T[T[x].pl].tans,T[T[x].pr].tans); if (T[x].tans==T[T[x].pl].tans) T[x].tnum=T[x].tnum+T[T[x].pl].tnum; if (T[x].tans==T[T[x].pr].tans) T[x].tnum=(T[x].tnum+T[T[x].pr].tnum)%P; } int build(int l, int r){ int p=++place; T[p].l=l; T[p].r=r; T[p].ans=-inf; T[p].ansnum=0; T[p].tans=-inf; if (l==r){ T[p].mn=d[l]; T[p].mx=c[l]; T[p].mxid=l;} else { int mid=(T[p].l+T[p].r)>>1; T[p].pl=build(l,mid); T[p].pr=build(mid+1,r); T[p].mn=min(T[T[p].pl].mn,T[T[p].pr].mn); T[p].mx=max(T[T[p].pl].mx,T[T[p].pr].mx); if (T[p].mx==T[T[p].pl].mx) T[p].mxid=T[T[p].pl].mxid; else T[p].mxid=T[T[p].pr].mxid; } return p; } int querymin(int p, int l, int r){ if (T[p].l==l&&T[p].r==r) return T[p].mn; int mid=(T[p].l+T[p].r)>>1; if (r<=mid) return querymin(T[p].pl,l,r); else if (l>mid) return querymin(T[p].pr,l,r); else return min(querymin(T[p].pl,l,mid),querymin(T[p].pr,mid+1,r)); } int query_max_id(int p, int l, int r){ if (T[p].l==l&&T[p].r==r) return T[p].mxid; int mid=(T[p].l+T[p].r)>>1; if (r<=mid) return query_max_id(T[p].pl,l,r); else if (l>mid) return query_max_id(T[p].pr,l,r); else { int now1=query_max_id(T[p].pl,l,mid), now2=query_max_id(T[p].pr,mid+1,r); if (c[now1]>=c[now2]) return now1; else return now2; } } void modify(int p, int x, int ans, int ansnum){ if (T[p].l==T[p].r){T[p].tans=ans; T[p].tnum=ansnum; return;} int mid=(T[p].l+T[p].r)>>1; if (mid>=x) modify(T[p].pl,x,ans,ansnum); else modify(T[p].pr,x,ans,ansnum); change(p); } void modify_tag(int p, int l, int r, int ans, int ansnum){ if (T[p].l==l&&T[p].r==r){ if (T[p].ans<ans) T[p].ans=ans, T[p].ansnum=0; if (T[p].ans==ans) { T[p].ansnum=(T[p].ansnum+ansnum); if (T[p].ansnum>P) T[p].ansnum-=P; } return; } int mid=(T[p].l+T[p].r)/2; if (mid>=r) modify_tag(T[p].pl,l,r,ans,ansnum); else if(mid<l) modify_tag(T[p].pr,l,r,ans,ansnum); else {modify_tag(T[p].pl,l,mid,ans,ansnum); modify_tag(T[p].pr,mid+1,r,ans,ansnum);}; } void query_tag(int p, int x){ if (T[p].ans>f[x]) f[x]=T[p].ans, num[x]=0; if (T[p].ans==f[x]) { num[x]=(num[x]+T[p].ansnum); if (num[x]>P) num[x]-=P; } if (T[p].l==T[p].r) return; int mid=(T[p].l+T[p].r)>>1; if (mid>=x) query_tag(T[p].pl,x); else query_tag(T[p].pr,x); } void getans(int p, int l, int r){ if (T[p].l==l&&T[p].r==r){ if (T[p].tans>nowans) nowans=T[p].tans, nownum=0; if (T[p].tans==nowans) nownum=(nownum+T[p].tnum)%P; return; } int mid=(T[p].l+T[p].r)>>1; if (mid>=r) getans(T[p].pl,l,r); else if (mid<l) getans(T[p].pr,l,r); else {getans(T[p].pl,l,mid); getans(T[p].pr,mid+1,r);} } int find_big(int pl, int pr, int x){ int num=pr+1; while (pl<=pr){ int mid=(pl+pr)>>1; if (g[mid]>=x){ pr=mid-1; num=mid; } else pl=mid+1; } return num; } void solve(int l, int r){ if (l>r) return; int mid=query_max_id(rt,l,r); solve(l,mid-1); int s=max(c[mid]+l-1,mid),nowl=max(l,g[s]), nowr=min(s-c[mid]+1,mid),lar; nowans=-inf; bool flag=true; if (nowl<=nowr&&s<=r){ getans(rt,nowl-1,nowr-1); if (s<=r){ if (f[s]<nowans+1) num[s]=0,f[s]=nowans+1; if (f[s]==nowans+1) num[s]=(num[s]+nownum)%P; } } else flag=false; for (s=s+1; s<=min(r,mid+c[mid]-2); s++){ if (nowl>mid) break; lar=nowr; nowr=min(nowr+1,mid); if (g[s]>nowl){ nowl=g[s]; nowans=-inf; if (nowl<=nowr) getans(rt,nowl-1,nowr-1); else flag=false; } else { if (flag==false){ if (nowl<=nowr){ flag=true; nowans=f[nowl-1]; nownum=num[nowl-1]; } } else { if (nowr>lar){ if (nowans<f[nowr-1]) nowans=f[nowr-1], nownum=0; if (nowans==f[nowr-1]) nownum=(nownum+num[nowr-1])%P; } } } if (flag==false) continue; if (f[s]<nowans+1) num[s]=0, f[s]=nowans+1; if (f[s]==nowans+1) { num[s]=(num[s]+nownum); if (num[s]>P) num[s]-=P; } } int b1=find_big(s,r,l), b2=find_big(s,r,mid+1); nowans=-inf, nownum=0; getans(rt,l-1,mid-1); if (s<=b1-1) modify_tag(rt,s,b1-1,nowans+1,nownum); for (int s=b1; s<b2; s++){ nowans=-inf, nownum=0; getans(rt,g[s]-1,mid-1); if (f[s]<nowans+1) num[s]=0, f[s]=nowans+1; if (f[s]==nowans+1) { num[s]=(num[s]+nownum); if (num[s]>P) num[s]-=P; } } query_tag(rt,mid); modify(rt,mid,f[mid],num[mid]); solve(mid+1,r); } int main(){ n=read(); for (int i=1; i<=n; i++) c[i]=read(), d[i]=read(); for (int i=1; i<=n; i++) f[i]=-inf; rt=build(0,n); g[1]=1; modify(rt,0,0,1); f[0]=0; num[0]=1; for (int i=2; i<=n; i++){ g[i]=g[i-1]; while (querymin(rt,g[i],i)<i-g[i]+1) g[i]++; } solve(1,n); if (f[n]<=0) printf("NIE\n"); else printf("%d %d\n",f[n],num[n]); return 0; }