这篇博客主要讲解树状数组一些练习例题。
MooFest poj1990
链接:http://poj.org/problem?id=1990
题意:任意两个牛想一起交谈,会产生一种volumes。volumes根据任意任意两个牛之间的距离乘以这两个牛之间说话的最大声音值,求出这个声音值的总和。
题解:最开始的想法就是暴力,但是数据比较多,时间复杂度是O(n^2)。所以需要寻找一种算法,因为这些牛排列的顺序的在一条直线上的,所以很容易想到用树状数组,树状数组时间复杂度(logN),所以怎么用。树状数组用来求前缀和的,所以就想到将声音强度v和位置id,用结构体存起来,然后按照v从小到大的顺序排列,让然后查找vi前面有多少距离之和,让此时位置id * i - vi 的前缀和,再用vi * |(id*i - vi)|。为什么这么做,因为我vi前面有m个数,m乘以我自己本身的位置的数,然后减去vi前m个数的前缀和的绝对值就是我要的结果。后来我发现这么做用问题,这么做只适合前m个数都比我自己本身的数大,或者小才可以这么做,如果前m个数有比既有我自己本身的数大的又有比我自己本身小的,那么这种办法不适用了,所以我就把比我大的分成一类,比我小的分成一类,这么做就没有问题了,此外还需要统计比我所在位置大的数的个数。
错误的方法:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define N 20000
int n, m;
int c[N];
struct Node{
int pos, val;
}a[N];
bool cmp(Node A, Node B){
return A.val < B.val;
}
int lowbit(int a){
return a & (-a);
}
void add(int pos, int val){
for(int i = pos; i <= m; i += lowbit(i)){
c[i] += val;
}
}
int sum(int pos){
int res = 0;
for(int i = pos; i > 0; i -= lowbit(i)){
res += c[i];
}
return res;
}
int main(){
while(~scanf("%d", &n)){
m = 0;
int ans = 0;
for(int i = 1; i <= n; i++){
cin>>a[i].val>>a[i].pos;
if(a[i].val > m)m = a[i].val;
}
sort(a+1, a+1+n, cmp);
for(int i = 1; i <= n; i++){
int pos = sum(a[i].val);
cout<<"pos"<<pos<<endl;
cout<<abs(a[i].pos*(i-1)- pos)<<endl;
ans += abs(a[i].pos*(i-1)- pos)*a[i].val;
cout<<abs(a[i].pos*(i-1)- pos)*a[i].val<<endl;
add(a[i].val, a[i].pos);
}
cout<<ans<<endl;
}
return 0;
}
正确的方法:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define N 20010
int n, m;
int c[N], b[N];
struct Node{
int id, v;
}a[N];
bool cmp(Node A, Node B){//v按照从小到大的顺序
return A.v < B.v;
}
int lowbit(int a){
return a & (-a);
}
void add(int *c, int pos, int val){
for(int i = pos; i <= N; i += lowbit(i)){
c[i] += val;
}
}
long long sum(int *c, int pos){
long long res = 0;
for(int i = pos; i > 0; i -= lowbit(i)){
res += c[i];
}
return res;
}
int main(){
while(~scanf("%d", &n)){
memset(c, 0, sizeof(c));//c用来统计前缀了
memset(b, 0, sizeof(b));//b用来统计个数
for(int i = 1; i <= n; i++){
cin>>a[i].v>>a[i].id;
}
long long up = 0, down = 0, up_count = 0, down_count = 0;//up统计比vi大的前缀和, down用来统计比vi小的前缀和
//up_conunt用来统计比vi大的个数,down_count用来统计比vi小的个数
sort(a+1, a+1+n,cmp);
long long Sum = 0, ans = 0;
for(int i = 1; i <= n; i++){
Sum += a[i].id;//Sum用来统计一共加入了总距离是多少
add(c, a[i].id, a[i].id);
down = sum(c, a[i].id);//比i的位置小的,包括i的位置,最后会减掉的
up = Sum - down;//比i大的
add(b, a[i].id, 1);
down_count = sum(b, a[i].id);//比i位置大的个数
up_count = i - down_count;//比i位置小的个数
ans += a[i].v*(up - up_count*a[i].id) + a[i].v*(down_count*a[i].id - down );
}
cout<<ans<<endl;
}
return 0;
}
Japan poj3067
链接:http://poj.org/problem?id=3067
题意:从日本东海岸有n个城市,西海岸有m个城市,在东海岸或者西海岸的从北到南按照从1, 2......从东海岸的到西海岸有k条公路,没条公路都是到直接连接东西海岸的一条直线,任意两条直线最多有一个交点,问有多少交点。
题解:本题把图画出来就能看出来了,有一个问题,起点或中点相同的不算是交点,画出图之后就把问题转化成求逆序数的问题,用一个结构体存xi, yi,让xi从小到大的顺序,然后按照这个顺序,用树状数组求yi的逆序数,因为数据不所以不用离散化处理。
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 1000005
int n, m, maxn = 0;
int c[N];
int lowbit(int a){
return a & (-a);
}
void add(int pos, int val){
for(int i = pos; i <= m; i += lowbit(i) ){
c[i] += val;
}
}
long long sum(int pos){
long long res = 0;
for(int i = pos; i > 0; i -= lowbit(i)){
res += c[i];
}
return res;
}
struct Node{
int l, r;
}a[N];
bool cmp(Node A, Node B){
if(A.l == B.l)return A.r < B.r;
else return A.l < B.l;//重组按照xi从小到大的顺序。如果xi相等的就按照
//yi大的放在前面排序的放在前面,为什么这么做,因为把起点一样的排除掉
}
int main(){
int T, k, cn = 0;
scanf("%d", &T);
while(T--){
cn++;
memset(c, 0, sizeof(c));
scanf("%d %d %d", &n, &m, &k);
for(int i = 1; i <= k; i++){
scanf("%d %d", &a[i].l, &a[i].r);
}
long long Sum = 0;
sort(a+1, a+1+k, cmp);
for(int i = 1; i <= k;i++){
add(a[i].r, 1);
Sum += i - sum(a[i].r);
}
printf("Test case %d: %lld\n", cn, Sum);
}
return 0;
}
敌兵布阵 hdu1166
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1166
题解:本题就是非常水的一道题,利用的原理就是利用树状数组的单点修改+区间查询,但是本题有一个注意事项就是不要用cin和cout会超时的
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
#define N 50005
int n;
int c[N];
int lowbit(int a){
return a & (-a);
}
void add(int pos, int val){
for(int i = pos; i <= n; i += lowbit(i)){
c[i] += val;
}
}
long long sum(int pos){
long long res = 0;
for(int i = pos; i > 0; i -= lowbit(i)){
res += c[i];
}
return res;
}
int main(){
int T;
scanf("%d", &T);
int cn = 0;
while(T--){
cn++;
memset(c, 0, sizeof(c));//记得要初始化
printf("Case %d:\n", cn);
scanf("%d", &n);
int a;
for(int i = 1; i <= n; i++){
scanf("%d", &a);
add(i, a);
}
string s;
cin>>s;
int left, right, val, pos;
while(s != "End"){
if(s == "Query"){
scanf("%d %d", &left, &right);
printf("%lld\n",sum(right) - sum(left - 1));
}
else if(s == "Sub"){
scanf("%d %d", &pos, &val);
add(pos, -val);
}
else {
scanf("%d %d", &pos, &val);
add(pos, val);
}cin>>s;
}
}
return 0;
}
Color the ball hdu1556
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1556
题解:本题也是非常水的树状数组类的题型,利用树状数组的区间修改+单点查询,没有什么特别的注意事项。
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 100005
int c[N];
int n, m;
int lowbit(int a){
return a & (-a);
}
void add(int pos, int val){
for(int i = pos; i <= n; i += lowbit(i)){
c[i] += val;
}
}
int sum(int pos){
int res = 0;
for(int i = pos; i > 0; i -= lowbit(i)){
res += c[i];
}
return res;
}
void add_range(int l, int r, int val){
add(l, val);
add(r+1, -val);
}
int main(){
while(scanf("%d", &n) != EOF && n != 0){
int l, r;
memset(c, 0, sizeof(c));
for(int i = 1; i <= n; i++){
scanf("%d %d", &l, &r);
add_range(l, r, 1);
}
for(int i = 1; i <= n; i++){
if(i != 1)cout<<" ";
printf("%d", sum(i));
}
printf("\n");
}
return 0;
}
Stars poj2352
链接:http://poj.org/problem?id=2352
题意:就是一个给一个二维的坐标问他左下角有多少个点在他左下角,他就是什么级别的,此外y是单调不减的,问从1到n-1,每个级别有多少个。
题解:本题是一个二维的坐标但是y是单调不减的所以就把二维的看成一维的所以只需要看x轴就行了。
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
#define N 32001
int c[N], sum[N];
int lowbit(int a)
{
return a & (-a);
}
int n;
void add(int pos)
{
for(int i = pos; i <= N; i += lowbit(i))
{
c[i]++;
}
}
int Getsum(int pos)
{
int res = 0;
for(int i = pos; i > 0; i -= lowbit(i))
{
res += c[i];
}
return res;
}
int main()
{
scanf("%d", &n);
memset(c, 0, sizeof(c));
memset(sum, 0, sizeof(sum));
int x, y;
int m = n;
while(m--)
{
scanf("%d %d", &x, &y);
sum[Getsum(x+1)]++;//sum用来统计每个几倍有多少个数
add(x+1);
}
for(int i = 0; i < n; i++)
{
printf("%d\n", sum[i]);
}
return 0;
}