【树状数组】专题+模板

先放模板。。。

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

bool cmp(int b,int c) {
    return a[b]<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;
} 

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

const int N=1005,NN=3000;  
const int M=1e9+7;  
  
int dp[N][N],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[N];  写成这样就会超时?? 
int f;
int y[N],st[N],ed[N];
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[N],y[N];
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[N];
int y[N],z[N],w[N];
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[n]);
	} 
    return 0;  
}  


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值