Sie sind auf Seite 1von 6

Más Siguiente blog» thexfer@gmail.

com Panel Cerrar sesión

The blue path


Algorithms, algorithms and more algorithms...

Wednesday, November 21, 2012

UVA 12532 - Interval Product


http://uva.onlinejudge.org/external/125/12532.html

You are given a sequence of N (1 ⩽ N ⩽ 10 5 ) integers X1 , X2 , ⋯ , Xn . You have to perform


K (1 ⩽ K ⩽ 10 ) operations over this sequence, which can be:
5

(1) Change an arbitrary value of the sequence.

(2) Given a range [i, j] return if the product Xi , Xi+1 , . . . , Xj−1 , Xj is positive, negative or zero.

As usual we are going to start asking ourselves if it's possible to solve this problem under the 2
seconds time constraint using the brute force approach? The answer is "maybe" after the UVA
administrator install the new Quantum Computers servers :), because this is not the case a Brute
Force solution is going to timeout.

To solve this problem I am going to describe two approaches, the first one using Segment Tree and
the second one using Binary Indexed Trees (BIT):

In case that the reader is not familiar with this data structures I personally recommend the following
TopCoder tutorials:

1. Binary Indexed Trees by boba5551


2. Range Minimum Query and Lowest Common Ancestor by danielp

(1) Solution using Segment Tree

The main problem of the Brute Force approach is that we need O(j − i + 1) time to answer each of
the second operation queries. Using Segment Trees we can reduce this time to O(logN ) where N is
the size of our sequence.

The main idea is to construct a Segment Tree of the sub-intervals products of our sequence, for
example, given the sequence (−2, 6, 0, −1) we got the following tree:

Labels

ad hoc (2) algorithms (2) arithmetic


progression (1) arithmetic sequence (1) bfs (1)
binary indexed trees (2) binary search (2)
BIT (2)brute force (8) C++ (2)
codeforces (11) codejam (1)
combinatorics (3) connected components (1)
dfs (5) disjoint sets
counting inversions (1)
(4) divisors (1) dynamic programming
(7) euler's formula (1) fenwick tree (1) fibonacci
numbers (1) gcd (3) geometry (2) google (1)
graph theory (5) greedy (7)
implementation (6) infinite geometric series

There is just a little issue with this, the values of the given sequence are in the range (1) lcm (3) math (19) number
−100 ⩽ Xi ⩽ 100 . This implies that we are in risk to get an overflow/underflow for some theory (7) perfect squares (1) planar graph
sequences, an easy one is (100, 100, 100, 100, 100), if we execute the second operation over (1) prime factors (3) probability (1)
range [0, 4] we got the value 1005 that clearly surpass the 32 bit integer capacity. programming (1) programming contest (2)
recursion (5) Segment tree (1) sieve of
Let's remember that this problem care just for the sign (e.g. negative, positive) between certain eratosthenes (2) simulation (2) sorting (3)
interval [i, j] . So to avoid the overflow problem we simply need to substitute all the negative numbers strings (5) topcoder (2) tree (1) triangular
with -1 and all the positives values by 1. After applying the previous operations we got a tree that look numbers (2) trie (1) tries (1) two pointers (2)
like this: uva (12)
Blog Archive

► 2013 (4)
▼ 2012 (29)
▼ November (2)
UVA 12532 - Interval
Product
UVA 11076 - Add Again

► October (2)
► September (2)
► August (3)
This solution will give us an overall time complexity of O(K logN ) which is enough to pass the 2 ► July (11)
seconds time limit.
► June (7)
► May (2)

1 #include <iostream> ? ► 2011 (1)


2 #include <cstdio>
3 #include <vector>
4 #include <cmath>
5
6 #define DEBUG(x) cout << #x << ": " << x << endl About Me
7
8 using namespace std; Pavel Simo
9 typedef vector<int> VI;
10 View my complete profile
11 class SegmentTree {
12
13 public:
14 VI A, tree;
15 Followers
16 SegmentTree(VI &_A) : A(_A) {
17 int N = A.size(); Seguidores (19)
18 tree = VI(4 * N, 1);
19 build(1, 0, N - 1);
20 }
21
22 void build(int node, int lo, int hi) {
23 if(lo == hi) {
24 tree[node] = A[lo];
25 return;
26 }
27
28 int mid = (lo+hi)/2;
29
30 //compute the values in the left and right subtrees
Seguir
31 build(2*node, lo, mid);
32 build(2*node + 1, mid + 1, hi);
33
34 // each parent node is equal to the product of his childs
35 tree[node] = tree[2*node] * tree[2*node+1];
36 }
37
38 int query(int i, int j) {
39 return _query(1, 0, A.size() - 1, i, j);
40 }
41
42 int _query(int node, int lo, int hi, int i, int j) {
43
44 // if the current interval doesn't intersect the query
45 // interval return 1, this value is not going to affect
46 // the sign of the final product
47 if(hi < i || lo > j)
48 return 1;
49
50 // if the current interval is included in the query
51 // interval return tree[node]
52 if(lo >= i && hi <= j)
53 return tree[node];
54
55 int mid = (lo + hi) / 2;
56
57 // product of the intervals included in the query
58 return _query(2*node, lo, mid, i, j) *
59 _query(2*node+1, mid + 1, hi, i, j);
60 }
61
62 void update(int ind, int val) {
63 _update(1, ind, val, 0, A.size() - 1);
64 }
65
66 void _update(int node, int ind, int val, int lo, int hi) {
67
68 // if the current index is not contained in the interval,
69 // skip this interval
70 if(ind < lo || ind > hi)
71 return;
72
73 if(lo == hi) {
74 tree[node] = val;
75 return;
76 }
77
78 int mid = (lo + hi) / 2;
79 // update the values in the left and right subtrees
80 _update(2*node, ind, val, lo, mid);
81 _update(2*node + 1, ind, val, mid + 1, hi);
82
83 tree[node] = tree[2*node] * tree[2*node+1];
84 }
85 };
86
87 vector<int> X;
88
89 int main() {
90 ios_base::sync_with_stdio(false);
91 cin.tie(NULL);
92 int n, q, x, ind, val, lo, hi, s;
93 char cmd;
94 while(cin >> n >> q) {
95 X.clear();
96 for(int i = 0; i < n; ++i) {
97 cin >> x;
98 // replace the positive values with 1's
99 // and the negative with -1's
100 if(x > 0) x = 1;
101 if(x < 0) x = -1;
102 X.push_back(x);
103 }
104 SegmentTree st(X);
105 while(q-- > 0) {
106 cin >> cmd;
107 if(cmd == 'C') {
108 cin >> ind >> val;
109 --ind;
110 // replace the positive values with 1's
111 // and the negative with -1's
112 if(val > 0) val = 1;
113 if(val < 0) val = -1;
114 st.update(ind, val);
115 X[ind] = val;
116 } else {
117 cin >> lo >> hi;
118 --lo, --hi;
119 s = st.query(lo, hi);
120 if(s > 0) cout << '+';
121 else if(s < 0) cout << '-';
122 else cout << '0';
123 }
124 }
125 cout << '\n';
126 }
127 return 0;
128 }

(2) Solution using Binary Indexed Trees

The solution with BIT is a little bit less intuitive than the previous exposed, but we can easily came
with it by asking ourselves the correct questions.

1. When the product of an interval [i, j] becomes zero?

When there is one or more zeros in the interval.

2. When the product of an interval [i, j] becomes negative?

When there is not zeros in the interval, and the total number of negative number between [i, j] is
odd.

3. When the product of an interval [i, j] becomes positive?

When none of the other conditions holds then product of the interval [i, j] is positive.

After considering these three questions it seems important to keep track of the frequency of zeros and
negative numbers for any interval [i, j] of our sequence. The easiest way to accomplish this task is
maintaining two arrays with the cumulative frequencies of both types, for example:

So if we want to know the numbers of zeros between certain interval [i, j] we just need to apply the
old trick of f req[j] − f req[i − 1] (assuming that f req[0] is 0).

f req[4] − f req[1] = 1

f req[4] = 2

f req[1] = 1

2−1 = 1

Until this point everything seems just fine, however there is another problem we haven't consider yet,
what happen when we update one of the values of our sequence (operation #1) ? if that's the case we
also need to update our cumulative frequency array. It's not hard to see that even when we can
perform operation #2 in O(1) time, the operation #1 is going to take in the worst case scenario O(N )
time, that due to the vast amount of queries we can't afford for this problem.

Considering this problems, the necessity of using BIT seems more evident. We just need to carefully
maintain the cumulative frequency updated after each operation is performed. The overall time
complexity of this solution is O(K logN ) .

1 #include <iostream> ?
2 #include <cstdio>
3 #include <vector>
4 #include <cmath>
5 #include <cstring>
6
7 #define DEBUG(x) cout << #x << ": " << x << endl
8
9 using namespace std;
10 typedef vector<int> VI;
11
12 const int MAXN = 1000002;
13 int zero[MAXN], neg[MAXN] , X[MAXN];
14
15 int read(int *tree, int idx) {
16 int res = 0;
17 while(idx > 0) {
18 res+=tree[idx];
19 idx-=(idx & -idx);
20 }
21 return res;
22 }
23
24 void update(int *tree, int idx, int val) {
25 while(idx < MAXN) {
26 tree[idx]+=val;
27 idx +=(idx & -idx);
28 }
29 }
30
31 int main() {
32 ios_base::sync_with_stdio(false);
33 cin.tie(NULL);
34 int n, q, x, ind, val, lo, hi;
35 char cmd;
36 while(cin >> n >> q) {
37 memset(zero, 0, sizeof(zero));
38 memset(neg, 0, sizeof(neg));
39 for(int i = 1; i <= n; ++i) {
40 cin >> x;
41 if(x < 0) {
42 // increase the cumulative frequency of the negative values
43 update(neg, i, +1);
44 } else if(x == 0) {
45 // increase the cumulative frequency of the zeros
46 update(zero, i, +1);
47 }
48 X[i] = x;
49 }
50 while(q-- > 0) {
51 cin >> cmd;
52 if(cmd == 'C') {
53 cin >> ind >> val;
54 if(val < 0) {
55 // negative value replace zero or positive
56 // increase the negatives cumulative frequency
57 if(X[ind] >= 0) update(neg, ind, +1);
58
59 // negative value replace zero
60 // decrease the zeros cumulative frequency
61 if(X[ind] == 0) update(zero, ind, -1);
62 } else if(val == 0) {
63
64 // zero value replace positive or negative
65 // increase the zeros cumulative frequency
66 if(X[ind] != 0) update(zero, ind, +1);
67
68 // zero value replace positive or negative
69 // decrease the negatives cumulative frequency
70 if(X[ind] < 0) update(neg, ind, -1);
71 } else {
72 // positive value replace zero
73 // decrease the zeros cumulative frequency
74 if(X[ind] == 0) update(zero, ind, -1);
75
76 // positive value replace negative
77 // decrease the negatives cumulative frequency
78 if(X[ind] < 0) update(neg, ind, -1);
79 }
80 X[ind] = val;
81 } else {
82 cin >> lo >> hi;
83
84 // total count of zeros
85 int z = read(zero, hi) - read(zero, lo - 1);
86
87 // total count of negatives
88 int m = read(neg, hi) - read(neg, lo - 1);
89
90 if(z > 0) cout << '0';
91 else if(m & 1) cout << '-';
92 else cout << '+';
93 }
94 }
95 cout << '\n';
96 }
97 return 0;
98 }

Posted by Pavel Simo at 8:13 AM

Labels: binary indexed trees, BIT, math, Segment tree, uva

3 comments:
Surge said...
Thanks mate! you have been a great help :)
6:30 PM

Pavel Simo said...


You're welcome :)
7:46 PM

Anand said...
Very nice tutorial. It finally helped me getting accepted my first Segment tree submission
in JAVA at UVA..
Thanks bro.. :)
11:42 AM

Post a Comment
Newer Post Home Older Post

Subscribe to: Post Comments (Atom)

Awesome Inc. theme. Powered by Blogger.

Das könnte Ihnen auch gefallen