POJ 1769 Minimizing maximizer
线段树之点树: 将最大的数字分离到最后的一位!
题意分析: 如果我们考虑将数组看成一条[1, n]的线段, 而每项操作也看成是从[ i[k], j[k] ]的线段, 那么题意就是按照线段的输入顺序, 将线段[1, n]从左到右依次覆盖, 问题变成求最小的覆盖线段总数.
算法思想: 考虑最基本的规划方法, 用Opt[k] 表示覆盖掉[1, k]的线段最少需要的步数, 那么状态转移方程为:
Opt[k] = min { Opt[d] + 1 | j[p] = k && d >= i[p] && d <= j[p] && k > 1 }
其中i[p], j[p]分别表示线段p的左端点和右端点; 预处理: Opt[1] = 0 .
最后的答案就是Opt[n], 但是考虑时间复杂度是O(m^2), m 最大为500000, 超时无疑.但是这里我们看到了规划的决策集合是一条连续的线段, 是要在这条线段上面取得最小值, 那么线段树的结构就正好适用.
由于这里最小的单位是一个点, 所以我们采取线段树的第一种变化: 把元线段设置为单位点, 即[k, k], 在规划的时候维护线段树即可.
另外, 线段树结点结构中, 需要加入元素 xmin, 代表最少需要用到的覆盖线段数目可以覆盖到当前结点所代表的线段.
p.s. : 如果题目不要求按照顺序选择线段从左到有覆盖的话,可以对线段进行排序,然后贪心即可.
#include <stdio.h>
const int N = 50001;
const int MAX = 500001;
struct TreeNode
{
int b, e; // [b, e]
int xmin;
}node[N*3];
// 建立线段树(点树)
void Bulid(int p, int l, int r)
{
node[p].b = l; node[p].e = r; node[p].xmin = MAX;
if( l < r )
{
Bulid(p*2, l, (l+r) >> 1);
Bulid(p*2+1, ((l+r) >> 1) + 1, r);
}
}
// 维护xmin
void Insert(int p, int r, int val)
{
if( node[p].xmin > val ) node[p].xmin = val;
if( node[p].b != node[p].e )
{
int m = (node[p].b+node[p].e) >> 1;
if( r <= m ) Insert(p*2, r, val);
else Insert(p*2+1, r, val);
}
}
// 取[l, r]的最小值, 以求[l, r+1]的最小值, 只需记录右端点即可
int GetMin(int p, int l, int r)
{
// 如果[l, r]完全覆盖node[p], 直接返回node[p]的最小值
if( node[p].b >= l && node[p].e <= r ) return node[p].xmin;
int m = (node[p].b+node[p].e) >> 1;
int t1 = MAX, t2 = MAX;
if( r <= m ) t1 = GetMin(p*2, l, r);
else if( l > m ) t2 = GetMin(p*2+1, l, r);
else
{
t1 = GetMin(p*2, l, m);
t2 = GetMin(p*2+1, m+1, r);
}
return (t1 > t2 ? t2 : t1);
}
int main(void)
{
int n, m;
while( scanf("%d%d", &n, &m) != EOF )
{
Bulid(1, 1, n); // 建树
Insert(1, 1, 0); // opt[1] = 0;
while( m-- )
{
int left, right;
scanf("%d%d", &left, &right);
if( left < right )
{
int x = GetMin(1, left, right-1);
Insert(1, right, x+1); // 只更新右端点
}
//printf("OK!~~\n");
}
printf("%d\n", GetMin(1, n, n));
}
return 0;
}