【BZOJ 3711】[PA2014] Druzyny

题目:

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(1n1000000),表示小朋友的数目。
接下来 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](1c[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]ijk=j+1miniD[k]
上界有明显单调性: i ≤ min ⁡ k = j + 1 i D [ k ] + j i \le \min_{k=j+1}^{i}D[k] + j ik=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] jik=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]ij考虑先找出 [ 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,k1]
  • [ l , k − 1 ] [l,k-1] [l,k1]的答案更新 [ 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,k1]
**-> 3 **用 [ l , k − 1 ] [l,k-1] [l,k1]的答案更新 [ k , r ] [k,r] [k,r]的答案,据左式有: j ≤ i − C [ k ] j\le i-C[k] jiC[k]

  • i i i的取值范围
    • i &lt; l + C [ k ] i&lt;l+C[k] i<l+C[k]时, j &lt; l j&lt;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 &lt; k + C [ k ] i&lt; 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),iC[k]]。对于第一个 i i i,直接去线段树上找最大值,以后当 i i i增加,若只有右端点增加,只需用上一次取出的最大值更新一次答案就好了,如果左端点增加,去线段树再找一遍最大值即可。
    • i ≥ k + C [ k ] i\ge k+C[k] ik+C[k]时, j j j的合法区间都是 [ m a x ( g [ i ] , l ) , k − 1 ] [max(g[i],l),k-1] [max(g[i],l),k1]
      • 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],k1],所以可以用线段树区间更新。
    • 如果当前步骤没有合法的 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值