题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4614
分析:
此题唯一需要注意到的是,当我们进行操作1去找第一个和最后一个的时候,可以使用二分找答案的方式进行求解。
线段树中sum:表示区间L~R中空花瓶的总数。
而lazy则表示懒标记。用于区间修改,变为1或者变为0.
首先我们将空花瓶的时候定义为1. 所以在刚开始build()的时候,初始化他们全部为1.
当我们使用操作2,扔花的时候,就是区间修改为0
而当我们使用操作1,种花的时候,就是区间修改为1.
操作1的时候,要求出区间L~R中种了的花的数量。先用query()查询总和,就是总共的空花瓶数。然后L~R的总花瓶数 - 空花瓶。
然后将区间修改为1.modify()操作。
操作2的时候,就要用到二分的操作。
二分找到第一个 总和 >= 1的节点下标(query()查询操作)。就是初始种花坐标
二分找到第一个 为 min(总空瓶,f)的坐标,因为可能f大于总空瓶数,作为最终种花位置。
代码实现:
# include <iostream>
using namespace std;
const int N = 50011;
struct Node
{
int l,r;
int sum; // l~r区间内空花瓶个数
int lazy; //懒标记
}edgs[N * 4];
int t;
int n,m;
void pushup(int u)
{
edgs[u].sum = edgs[2 * u].sum + edgs[2 * u + 1].sum;
}
void pushdown(int u)
{
if(edgs[u].lazy != -1)
{
edgs[2 * u].lazy = edgs[u].lazy;
edgs[2 * u + 1].lazy = edgs[u].lazy;
edgs[2 * u].sum = (edgs[2 * u].r - edgs[2 *u].l + 1) * edgs[u].lazy;
edgs[2 * u + 1].sum = (edgs[2 * u + 1].r - edgs[2 * u + 1].l +1) * edgs[u].lazy;
edgs[u].lazy = -1;
}
}
void build(int u , int l ,int r)
{
edgs[u].l = l , edgs[u].r = r , edgs[u].lazy = -1;
if(l == r)
{
edgs[u].sum = 1;
return;
}
else
{
int mid = (edgs[u].l + edgs[u].r) / 2;
build(2 * u , l , mid);
build(2 * u + 1 , mid +1 , r);
pushup(u);
}
}
int query(int u , int l , int r)
{
if(l <= edgs[u].l && edgs[u].r <= r)
{
return edgs[u].sum;
}
else
{
pushdown(u);
int mid = (edgs[u].l + edgs[u].r) / 2;
int ans = 0;
if(l <= mid)
{
ans += query(2 * u , l , r);
}
if(r > mid)
{
ans += query(2 * u + 1, l , r);
}
return ans;
}
}
void modify(int u , int l , int r , int k)
{
if(l <= edgs[u].l && edgs[u].r <= r)
{
edgs[u].lazy = k;
edgs[u].sum = (edgs[u].r - edgs[u].l + 1) * k;
}
else
{
pushdown(u);
int mid = (edgs[u].l + edgs[u].r) / 2;
if(l <= mid)
{
modify(2 * u , l , r , k);
}
if(r > mid)
{
modify(2 * u + 1 , l , r , k);
}
pushup(u);
}
}
// 下标为0 ~ n - 1
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&m);
int k;
build(1,0,n - 1);
for(int j = 1 ; j <= m ; j++)
{
scanf("%d",&k);
if(k == 2) //查l,r区间插入了多少花
{
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",r - l + 1 - query(1,l,r));
modify(1,l,r,1);
}
else
{
int a,f; // a为开始位置,f为花的数量
scanf("%d %d",&a,&f); // 从a开始放f个
if(query(1,a,n - 1) == 0) // 如果为0,说明全部都有花,则输出Can not put any one.
{
printf("Can not put any one.\n");
}
else
{
int l1 = a , r1 = n - 1; // 左区间,右区间,找边界, <= f的第一个值
while(l1 < r1)
{
int mid = (l1 + r1)/2;
if( query(1,a,mid) > 0) // 如果a~mid存在空的,则区间就在a~mid
{
r1 = mid;
}
else
{
l1 = mid + 1;
}
}
printf("%d ",l1);
/*
// 为什么不能使用 <= 进行二分呢?比如现在到i的时候为可以种花,恰好为f个,而后面被中了花,则我们最后找到的下标是后面被中了花的部分。
l1 = a , r1 = n - 1;
while(l1 < r1) // 二分
{
int mid = (l1 + r1 + 1) / 2;
if( query(1,a,mid) <= f) //a ~ mid种花,如果值 >= f说明肯定可以,因为1代表空,可以放
{
l1 = mid;
}
else
{
r1 = mid - 1;
}
}
// 最后的终点就是l1
printf("%d\n",l1);
*/
l1 = a , r1 = n - 1;
while(l1 < r1) // 二分
{
int mid = (l1 + r1) / 2;
if( query(1,a,mid) >= min(query(1,a,n-1),f) ) //a ~ mid种花,如果值 >= f说明肯定可以,因为1代表空,可以放
{
r1 = mid;
}
else
{
l1 = mid + 1;
}
}
printf("%d\n",l1);
modify(1,a,l1,0); // a~l1都被种树了。
}
}
}
printf("\n");
}
return 0;
}