您的位置:首页 > 其它

【树状数组】专题+模板

2015-12-02 20:07 281 查看
先放模板。。。

#define lowbit(x) (x) & (-x)
const int N=1005;
const int M=1e9+7;
//【修改点(向上),求区间(向下)】の模板
int dp

,c[NN]
;
int a
,r
,w
;

bool cmp(int b,int c) {
return a<a[c];
}
void update(int i,int j,int value){
while(i<=NN){ //这个NN必须足够大(N*2以上)
c[i][j]=(c[i][j]+value%M)%M;
i+=lowbit(i);
}
}
int sum(int i,int j){  //i不能<=0,因此有些题目要平移值
int s=0;
while(i>0){
s=(s+c[i][j])%M;
i-=lowbit(i);
}
return s%M;
}
int main(){
int t,cnt=0;
cin>>t;
while(t--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
memset(c,0,sizeof(c));
memset(dp,0,sizeof(dp));
for(int i=0;i<=n;++i)
r[i]=i;          //存下标
sort(r+1,r+n+1,cmp); //下标根据a[]的大小排序
for(int i=1;i<=n;++i)
w[r[i]]=i;       //把a[]哈希一下(原本400,500,600的会变成1,2,3)
for(int i = 1; i <= n; ++i) {
dp[w[i]][1]=1;
update(w[i],1,1);
for(int j=2;j<=m;++j) {
dp[w[i]][j]=sum(w[i]-1,j-1); //表示w数组到i之前末位小于w[i]的长度为j-1的子序列个数,即Σdp[k][j-1](1<=k<w[i])
update(w[i], j, dp[w[i]][j]);//把dp[w[i]][j]放进对应的位置来
}
}
int ans = 0;
//for(int i=1;i<=n;++i) ans=(ans+dp[i][m])%M; 可以这样写,或者sum(n,m)
printf("Case #%d: %d\n",++cnt,sum(n,m) % M);
}
return 0;
}


我也想过写成:

for(int j=2;j<=m;++j){
dp[id][j]=dp1[id-1][j-1];
dp1[id][j]+=dp1[id-1][j];
}


但id的值可能会跳跃的,比如上面的r[]={1,3,5,2,4},算第三个dp[5][j]+=dp1[4][j-1],但此时还没有dp1[4][]的值,所以不能累加。这时就体现出了树状数组的作用。

【求前缀和为k的下标位置,和sum()刚好相反】

int find(int k){
int cnt = 0, ans = 0;
for (int i = 20; i >= 0; --i){ //2^20大于50000
ans += (1 << i);
if (ans >= n || cnt + c1[ans] >= k) ans -= (1 << i);
else cnt += c1[ans];
}
return ans+1;
}
【修改点的值,求区间最值】

void update2(int ii,int val){ //修改点
w[ii]=val;
for(int i=ii; i<=n+5; i+=lowbit(i)){
if(val>c2[i])
c2[i]=val;
else
break;
}
}
int query(int l, int r){
int s=w[r];//上边界
while (l!=r){
for(r-=1;r-lowbit(r)>=l;r-=lowbit(r)){
s=max(s,c2[r]);//注意计算区间,不要夸区间
}
s=max(s,w[r]);  //下边界
}
return s;
}


[b]【POJ 2155】 
 http://poj.org/problem?id=2155 二维矩阵树状数组(修改区间,求点的值)

const int N=1005,NN=3000;
const int M=1e9+7;

int dp

,c1[NN][NN];

int n,m,a,b,c,d;
void update(int ii,int jj,int val){ //修改区间(向下修改)
for(int i=ii; i>0; i-=lowbit(i)){
for(int j=jj; j>0; j-=lowbit(j)){
c1[i][j]=(c1[i][j]+val);
}
}
}
int sum(int ii,int jj){  //求某点的值(向上统计)
int s=0;
for(int i=ii; i<=NN; i+=lowbit(i)){
for(int j=jj; j<=NN; j+=lowbit(j)){
s+=c1[i][j];
}
}
return s;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
memset(c1,0,sizeof(c1));
char cc[2];
scanf("%d%d",&n,&m);
for(int i=0;i<m;++i){
scanf("%s",&cc);
if(cc[0]=='C'){
scanf("%d%d%d%d",&a,&b,&c,&d);
a++;b++;c++;d++;
update(a-1,b-1,1);
update(a-1,d,-1);
update(c,b-1,-1);
update(c,d,1);
}
else{
scanf("%d%d",&a,&b);
a++;b++;
printf("%d\n",sum(a,b)%2);
}
}
printf("\n");
}
return 0;
}


【POJ2299】 求至少交换几次相邻元素使得数列递增(很难想到可以用树状数组做)http://poj.org/problem?id=2299
http://blog.csdn.net/lyy289065406/article/details/6647346
本质是冒泡排序,但却还可以用归并排序求解。。。http://www.cnblogs.com/gj-Acit/archive/2013/08/10/3250525.html

const int N=500005,NN=1500005;
const int M=1e9+7;

ll c1[NN];

int n,m;
ll a;
void update(ll ii,ll val){ //修改点
for(int i=ii; i<=NN; i+=lowbit(i)){
c1[i]=(c1[i]+val);
}
}
int sum(ll ii){  //求区间
ll s=0;
for(int i=ii; i>0; i-=lowbit(i)){
s+=c1[i];
}
return s;
}
int main(){
int n;
while(cin>>n&&n){
memset(c1,0,sizeof(c1));
ll s=0;
for(int i=0;i<n;++i){
cin>>a;
a++;
s+=i-sum(a-1);
update(a,1);
}
cout<<s<<endl;
}
return 0;
}


【POJ3321】 http://poj.org/problem?id=3321  (修改点,求区间值)

(题意转)给你一颗苹果树,树的主干设为1,每一个分支设为一个数,一直到N,代表这颗苹果树。每个分支上面只能最多有一个苹果,也就是一个枝子上面不可能有两个苹果,另外注意一点,不要把苹果树想象成二叉树,苹果树每个节点可以分出很多叉,应该是多叉树。

 

输入是叉之间的关系,

1 2

1 3

就是主干上面两个叉分别是2 和3.

 

下面是两种操作,Q 和C

C   j  的意思是如果 j 这个枝子上面有苹果就摘下来,如果没有,那么就会长出新的一个

Q  j  就是问 j 这个叉上面的苹果总数。

http://www.cnblogs.com/Jason-Damon/archive/2012/03/02/2376471.html

const int N=100005,NN=300005;
const int M=1e9+7;

int c1[NN];
vector<vector<int> > v(N);
//vector<int> v
;  写成这样就会超时??
int f;
int y
,st
,ed
;
void update(int ii,int val){
for(int i=ii; i<=NN; i+=lowbit(i)){
c1[i]=(c1[i]+val);
}
}
int sum(int ii){
int s=0;
for(int i=ii; i>0; i-=lowbit(i)){
s+=c1[i];
}
return s;
}
void dfs(int p){ //重新定义每个节点的标号,确定子树节点范围(关于这个的理解看链接)
st[p]=++f;
for(int i=0;i<v[p].size();++i){
dfs(v[p][i]);
}
ed[p]=f;
}
int main(){
int n,a,b;
scanf("%d",&n);
for(int i=0;i<n-1;++i){
scanf("%d %d",&a,&b);
v[a].push_back(b);
}
f=1; //下标从2开始
dfs(1);
int m;
scanf("%d",&m);
for(int i=0;i<m;++i){
char c[2];
scanf("%s %d",c,&a);
if(c[0]=='C'){
if(y[a]==0){
update(st[a],-1);
y[a]=1;
}
else{
update(st[a],1);
y[a]=0;
}
}
else if(c[0]=='Q'){
printf("%d\n",sum(ed[a])-sum(st[a]-1)+ed[a]-st[a]+1); //初始是都有苹果的
}
}
return 0;
}

hdu3450  lower_bound离散化大数值 http://acm.hdu.edu.cn/showproblem.php?pid=3450

给一些数,问有多少集合,满足相邻的数之间的差的绝对值小于d。

const int N=100005,NN=300005;
int x
,y
;
int c1[NN];

void update(int ii,int val){ //修改点
for(int i=ii; i<=NN; i+=lowbit(i)){
c1[i]=(c1[i]+val)%9901;
}
}
int sum(int ii){  //求区间
int s=0;
for(int i=ii; i>0; i-=lowbit(i)){
s=(s+c1[i])%9901;
}
return s;
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m)==2){
memset(c1,0,sizeof c1);
for(int i=1;i<=n;++i){
scanf("%d",&x[i]);
y[i]=x[i];
}
sort(y+1,y+n+1); //用y数组来离散化,避免值太大
for(int i=1;i<=n;++i){
int p=lower_bound(y+1,y+n+1,x[i]-m)-y;   //取下界
int q=upper_bound(y+1,y+n+1,x[i]+m)-y-1; //取上界
//【注意了!!结果a-b是负数的话要写成((a-b)%mod+mod)%mod,不能省略】
int s=((sum(q+1)-sum(p-1+1))%9901+9901)%9901;
int h=lower_bound(y+1,y+n+1,x[i])-y;
update(h+1,(s+1)%9901);
}
printf("%d\n",((sum(n+1)-(n%9901))%9901+9901)%9901);
}
return 0;
}

hdu5592  给出数组a[],a[i]表示b[]的前i个数的逆序对个数,让你还原这个数组b[] http://acm.hdu.edu.cn/showproblem.php?pid=5592

const int N=50005,NN=50005;
int x
;
int y
,z
,w
;
int c1[NN],c2[NN];
int n;
void update(int ii,int val){ //修改点
for(int i=ii; i<=n+5; i+=lowbit(i)){
c1[i]=(c1[i]+val);
}
}
int find(int k){ //【求前缀和为k的下标位置。和sum()刚好相反】
int cnt = 0, ans = 0;
for (int i = 20; i >= 0; --i){ //2^20大于50000
ans += (1 << i);
if (ans >= n || cnt + c1[ans] >= k) ans -= (1 << i);
else cnt += c1[ans];
}
return ans+1;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
memset(c1,0,sizeof(c1));
memset(z,0,sizeof(z));
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&x[i]);
update(i,1);
}
for(int i=n;i>=2;--i){
int p=x[i]-x[i-1];
y[i]=find(i-p);
update(y[i],-1);
z[y[i]]=1;
}
for(int i=1;i<=n;++i){
if(z[i]==0)
y[1]=i;
}
for(int i=1;i<=n-1;++i)
printf("%d ",y[i]);
printf("%d\n",y
);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: