题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4107
题意:给你一个数列,和一种操作,l,r,c,相当于给l,r的这个连续的区间上每个数的值+c,但是有个特殊的条件就是如果如果这个区间上的某个数的值>=P则加上的值为2*c,最后输出区间上每个位置的值。
思路:因为查询只有一次所以可以用离线的方法处理,然后对这样的区间加值感觉有很经典的做法就是打标记(对l的位置打一个c的标记(还需要记录标记发生的时间),对r+1的位置打一个-c的标记(同样也需要记录标记发生的时间)),我们可以对每个节点开一个vector来储存每个节点的标记,再维护一个容器储存当前所含有的标记,然后从第一个点开始来每次先将当前节点的标记全部全部加入容器中,容器中的标记按发生的时间顺序排序,然后对于当前节点将标记一个一个的加起来,当节点的值>=P的时候每次增加的值就变成二倍,当然如果就是这么一个一个加的话,那么跟纯模拟暴力的方法没有什么区别,而树状数组是一个很好的加速这一过程的工具,它的每个位置代表一个时间,这样就能很好的维护时间顺序,然后可以直接二分来找和>=P的最小位置pos,将pos之前的和加一倍上去,将pos之后的和加两倍上去。 就是这样了~~
还有一种线段树的做法,就是维护一个区间最大值和区间最小值,这种做法是在线的,其方法是如果当前区间的最大值<P则全部都加上C,如果当前区间的最小值>=P则全部都加上2*C,就是这个思路了,但是这个方法很容易T的,需要姿势很好才行.
code:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int,int> pp;
typedef long long LL;
const int maxn=(1<<18)+10;
int bit[maxn];
int n,m,P;
int sum(int i)
{
int s=0;
while(i>0){
s+=bit[i];
i-=(i&-i);
}
return s;
}
void add(int i,int x)
{
while(i<=m){
bit[i]+=x;
i+=(i&-i);
}
}
int getK(int K)
{
int ans=0,cnt=0;
for(int i=18;i>=0;i--){
ans+=(1<<i);
if(ans>=m||cnt+bit[ans]>=K) ans-=(1<<i);
else cnt+=bit[ans];
}
return ans+1;
}
vector<pp> vv[maxn];
int b[maxn];
int main()
{
int l,r,c;
while(scanf("%d%d%d",&n,&m,&P)!=EOF){
for(int i=0;i<=m+5;i++) bit[i]=0;
for(int i=0;i<=n;i++) vv[i].clear();
for(int k=1;k<=m;k++){
scanf("%d%d%d",&l,&r,&c);
vv[l].push_back(pp(k,c));
vv[r+1].push_back(pp(k,-c));
}
for(int i=1;i<=n;i++){
for(int j=0;j<vv[i].size();j++){
add(vv[i][j].first,vv[i][j].second);
}
int pos=getK(P);
b[i]=sum(pos)+2*(sum(m)-sum(pos));
}
for(int i=1;i<=n;i++){
printf("%d%c",b[i],i==n? '\n':' ');
}
}
return 0;
}
|