题目:
Description
体育课上, n n n个小朋友排成一行(从 1 1 1到 n n n编号),老师想把他们分成若干组,每一组都包含编号连续的一段小朋友,每个小朋友属于且仅属于一个组。
第 i i i个小朋友希望它所在的组的人数不多于 d [ i ] d[i] d[i],不少于 c [ i ] c[i] c[i],否则他就会不满意。
在所有小朋友都满意的前提下,求可以分成的组的数目的最大值,以及有多少种分组方案能达到最大值。
Input
第一行一个整数 n ( 1 ≤ n ≤ 1000000 ) n(1\le n\le 1000000) n(1≤n≤1000000),表示小朋友的数目。
接下来 n n n行,每行两个整数 c [ i ] , d [ i ] ( 1 ≤ c [ i ] ≤ d [ i ] ≤ n ) c[i],d[i](1\le c[i]\le d[i]\le n) c[i],d[i](1≤c[i]≤d[i]≤n),表示 i i i所在组的人数的最小值和最大值。
Output
如果不存在这样的方案,仅输出一行 N I E NIE NIE。
否则输出一行包含两个整数,组的数目的最大值、方案数量。(方案数量对 1000000007 1000000007 1000000007取模)
Sample Input
样例输入1:
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
样例输出1:
5 2
样例输出2:
NIE
试数据下载
密码: vjyz
思路:
设结构体数组
f
[
i
]
f[i]
f[i]表示
[
1
,
i
]
[1,i]
[1,i]可划分的最大区间个数是多少和方案数是多少。重载结构体的
+
+
+运算符,如果两个相加的元素最大区间相同,返回最大区间与两元素方案数之和,否则返回较大的那一个元素。
状态转移方程:
f
[
i
]
=
min
(
f
[
j
]
+
1
)
f[i]=\min(f[j]+1)
f[i]=min(f[j]+1)其中
j
j
j的合法范围是:
max
k
=
j
+
1
i
C
[
k
]
≤
i
−
j
≤
min
k
=
j
+
1
i
D
[
k
]
\max_{k=j+1}^{i}C[k] \le i-j \le \min_{k=j+1}^{i}D[k]
k=j+1maxiC[k]≤i−j≤k=j+1miniD[k]
上界有明显单调性:
i
≤
min
k
=
j
+
1
i
D
[
k
]
+
j
i \le \min_{k=j+1}^{i}D[k] + j
i≤k=j+1miniD[k]+j当
j
j
j减小时,
min
k
=
j
+
1
i
D
[
k
]
+
j
\min_{k=j+1}^{i}D[k] + j
mink=j+1iD[k]+j一定减小,所以我们一定可以找到一个位置
g
[
i
]
g[i]
g[i]使得
g
[
i
]
g[i]
g[i]右面的
j
j
j对
i
i
i全部合法,左边的全部不合法。
j
j
j的合法的范围是:
j
≥
i
−
min
k
=
j
+
1
i
D
[
k
]
j \ge i - \min_{k=j+1}^{i}D[k]
j≥i−k=j+1miniD[k]可以用单调队列维护区间最小值,
T
w
o
P
o
i
n
t
e
r
TwoPointer
TwoPointer枚举
i
i
i和
j
j
j,预处理出
g
[
i
]
g[i]
g[i]。
下界没有很好的性质:
max
k
=
j
+
1
i
C
[
k
]
≤
i
−
j
\max_{k=j+1}^{i}C[k] \le i-j
k=j+1maxiC[k]≤i−j考虑先找出
[
l
,
r
]
[l,r]
[l,r]最大的
C
[
k
]
C[k]
C[k],对
k
k
k进行分治,有4个步骤。
- 若 l = r l=r l=r,边界条件特殊处理
- 处理 [ l , k − 1 ] [l,k-1] [l,k−1]
- 用 [ l , k − 1 ] [l,k-1] [l,k−1]的答案更新 [ k , r ] [k,r] [k,r]的答案
- 处理 [ k , r ] [k,r] [k,r]
分治步骤详解:
**-> 0 **可以用线段树维护
C
C
C的最大值和最大值的位置,支持区间查询。
**-> 1 ** 若
l
=
r
l=r
l=r,把当前的
f
[
l
]
f[l]
f[l]与线段树中的
l
l
l位置的值用
log
n
\log n
logn的复杂度相互更新,因为一共
n
n
n个点,所以时间复杂度是
n
log
n
n \log n
nlogn的。
**-> 2 **递归处理
[
l
,
k
−
1
]
[l,k-1]
[l,k−1]。
**-> 3 **用
[
l
,
k
−
1
]
[l,k-1]
[l,k−1]的答案更新
[
k
,
r
]
[k,r]
[k,r]的答案,据左式有:
j
≤
i
−
C
[
k
]
j\le i-C[k]
j≤i−C[k]。
-
i
i
i的取值范围
- 当 i < l + C [ k ] i<l+C[k] i<l+C[k]时, j < l j<l j<l,显然没有合法的方案, 需要更新的 i i i的区间是 [ max ( l + C [ k ] , k ) , r ] [\max(l+C[k],k),r] [max(l+C[k],k),r]。
- 当没有合法的 i i i时,直接跳过,进入步骤4。
-
j
j
j的取值范围
- 当 i < k + C [ k ] i< k+C[k] i<k+C[k]时,对于每一个 i i i,有一个合法的 j j j的区间 [ m a x ( g [ i ] , l ) , i − C [ k ] ] [max(g[i],l),i-C[k]] [max(g[i],l),i−C[k]]。对于第一个 i i i,直接去线段树上找最大值,以后当 i i i增加,若只有右端点增加,只需用上一次取出的最大值更新一次答案就好了,如果左端点增加,去线段树再找一遍最大值即可。
- 当
i
≥
k
+
C
[
k
]
i\ge k+C[k]
i≥k+C[k]时,
j
j
j的合法区间都是
[
m
a
x
(
g
[
i
]
,
l
)
,
k
−
1
]
[max(g[i],l),k-1]
[max(g[i],l),k−1]
- 若 g [ i ] ≤ l g[i]\le l g[i]≤l,先二分出所有满足此条件的 i i i,用一次线段树更新所有满足此条件的 i i i。
- 其余的 i i i,对于一段 g [ i ] g[i] g[i]相同的 i i i来说, j j j的合法区间都是 [ g [ i ] , k − 1 ] [g[i],k-1] [g[i],k−1],所以可以用线段树区间更新。
- 如果当前步骤没有合法的 j j j,本轮不进行操作,继续枚举 i i i。
-> 4递归处理 [ k , r ] [k,r] [k,r]。
复杂度分析:
这样分治有
n
n
n层,每一层的操作中都有的常数次线段树操作,复杂度是
O
(
n
log
n
)
O(n\log n)
O(nlogn)的。
在
g
[
i
]
g[i]
g[i]右移进行的线段树操作中,因为分治的区间不会相交,所以每一个
i
i
i只有一次操作,每一个
j
j
j只有一次线段树操作,所以复杂度是
O
(
n
log
n
)
O(n\log n)
O(nlogn)的。
这样,总的复杂度就是
O
(
n
log
n
)
O(n\log n)
O(nlogn)的了。
线段树维护
f
f
f的方法:
这里的维护比较麻烦,要支持的操作是:选取区间中的最大值,若有多个最大值,要求他们的方案数之和。因为只有在递归到
l
=
r
l=r
l=r的时候,一个
f
[
l
]
f[l]
f[l]值才算是最终确定,现在要进行的所有询问操作一定在左边的区间里,修改操作一定在右边的区间里。
所以,对右面的修改操作标记永久化,即:需要打标记的时候,直接更新标记,不更新线段树的权值。
当我需要最终确定一个点的时候,把这个点当前的
f
f
f值看做一个标记,从线段树里一路找到该点,一路更新标记,最后修改线段树的叶子节点的值。
更新一个节点,当且仅当这个节点是一个叶子节点(被确定的值更新),或者这个节点的右儿子节点已经被更新。
这样的正确性显然,单次操作的复杂度都是
log
n
\log n
logn的。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1000010, p = 1000000007, inf = 2e9+1;
struct node{
int x, y;
node(){x = y = 0;}
node(int X, int Y){x = X, y = Y;}
bool operator < (const node &t)const{return x < t.x;}
inline node operator + (const node t)const{
if(x < t.x) return t;
if(x > t.x) return node(x, y);
return node(x, (y+t.y)%p);
}
inline node operator + (int X)const{return node(x+X, y);}
inline node operator += (node t){(*this) = (*this) + t; return *this;}
}f[maxn];
struct Point{
int x, y;
Point(int X=0, int Y=0){x = X, y = Y;}
bool operator < (const Point &t)const{return x == t.x ? y < t.y : x < t.x;}
};
struct Node{Point c; node v, tag;} tr[maxn*3];
int n, c[maxn], d[maxn], q[maxn], g[maxn];
#define mid ((l+r)>>1)
#define lch ((now<<1))
#define rch ((now<<1)|1)
void build(int now, int l, int r){
tr[now].tag = node(-inf, 0);
tr[now].v = node(-inf, -1);
if(l == r){
tr[now].c = Point(c[l], l);
if(l != 0) f[l] = node(-inf, 0);
else tr[now].v = node(0, 1), f[l] = node(0, 1);
return;
}
build(lch, l, mid), build(rch, mid+1, r);
tr[now].c = max(tr[lch].c, tr[rch].c);
}
Point cmax(int now, int l, int r, int pos1, int pos2){
if(l == pos1 && r == pos2) return tr[now].c;
if(pos2 <= mid) return cmax(lch, l, mid, pos1, pos2);
else if(pos1 >= mid+1) return cmax(rch, mid+1, r, pos1, pos2);
else return max(cmax(lch, l, mid, pos1, mid), cmax(rch, mid+1, r, mid+1, pos2));
}
node vmax(int now, int l, int r, int pos1, int pos2){
if(l == pos1 && r == pos2) return tr[now].v;
if(pos2 <= mid) return vmax(lch, l, mid, pos1, pos2);
else if(pos1 >= mid+1) return vmax(rch, mid+1, r, pos1, pos2);
else return vmax(lch, l, mid, pos1, mid) + vmax(rch, mid+1, r, mid+1, pos2);
}
void vadd(int now, int l, int r, int pos1, int pos2, node val){
if(l == pos1 && r == pos2){tr[now].tag += val; return;}
if(pos2 <= mid) vadd(lch, l, mid, pos1, pos2, val);
else if(pos1 >= mid+1) vadd(rch, mid+1, r, pos1, pos2, val);
else{
vadd(lch, l, mid, pos1, mid, val);
vadd(rch, mid+1, r, mid+1, pos2, val);
}
}
node vpadd(int now, int l, int r, int pos, node val){
if(l == r){tr[now].v = val + tr[now].tag; return tr[now].v;} node tmp;
if(pos <= mid) tmp = vpadd(lch, l, mid, pos, val + tr[now].tag);
else if(pos >= mid + 1) tmp = vpadd(rch, mid+1, r, pos, val + tr[now].tag);
if(tr[rch].v.y != -1) tr[now].v = tr[lch].v + tr[rch].v;
return tmp;
}
int find(int now, int r){
int l = now, ans = l-1;
while(l <= r){
int Mid = (l+r)/2;
if(g[Mid] == g[now]) ans = Mid, l = Mid + 1;
else r = Mid - 1;
} return ans;
}
int find2(int l, int r, int v){
int ans = l-1;
while(l <= r){
int Mid = (l+r)/2;
if(g[Mid] <= v) ans = Mid, r = Mid - 1;
else l = Mid + 1;
} return ans;
}
void Solve(int l, int r){
if(l == r){if(l) f[l] = vpadd(1, 0, n, l, f[l]); return;}
int k = cmax(1, 0, n, l+1, r).y;
Solve(l, k-1);
node lastans = node(-inf, 0); int ll = -1;
for(int i = max(c[k]+l, k); i < k + c[k]; i ++){
if(g[i] >= k || i > r){Solve(k, r); return;}
if(i-c[k] < max(g[i], l)) continue;
if(max(g[i], l) == ll) lastans += f[i-c[k]];
else lastans = vmax(1, 0, n, max(g[i], l), i-c[k]), ll = max(g[i], l);
f[i] += (lastans + 1);
}
lastans = node(-inf, 0);
int pos = find2(k+c[k], r, l);
if(k+c[k] <= pos){
lastans = vmax(1, 0, n, l, k-1);
vadd(1, 0, n, k+c[k], pos, lastans+1);
}
for(int i = pos+1; i <= r; i ++){
pos = find(i, r);
if(k-1 >= max(g[i], l)){
lastans = vmax(1, 0, n, max(g[i], l), k-1);
vadd(1, 0, n, i, pos, lastans+1);
}
i = pos;
}
Solve(k, r);
}
inline void gt(int &num){
char c; bool ok = 0; num = 0;
while(1){
c = getchar();
if(c <= '9' && c >= '0') num = num * 10 + c - '0', ok = 1;
else if(ok) return;
}
}
int main(){
gt(n);
for(int i = 1; i <= n; i ++) gt(c[i]), gt(d[i]);
build(1, 0, n);
int l = 1, r = 0, j = 0;
for(int i = 1; i <= n; i ++){
while(l <= r && d[q[r]] > d[i]) r --;
q[++ r] = i;
while(l <= r && j < i - d[q[l]]){
j ++;
while(l <= r && q[l] < j+1) l ++;
}
g[i] = j;
}
Solve(0, n);
f[n].y == 0 ? printf("NIE") : printf("%d %d", f[n].x, f[n].y);
return 0;
}