Yes, it is indeed possible, at least in some cases.
Basically, you need a way to efficiently store the lazy operation, and a way to efficiently combine two stored lazy operations into one.
For instance, say the update operation is segment assignment, that is, a[l] = x
, a[l+1] = x
, ...
, a[r-1] = x
, a[r] = x
.
This operation on the whole subtree can be stored as just the value x
, which will mean that the operation was to assign x
to every vertex of this subtree.
For lazy propagation in vertex v
, we just apply it to immediate children of vertex v
, and store the same lazy operation x
there.
Note that any old lazy operation in children is just erased by the assignment.
Such is the nature of assignment.
As for your added example, operation arr[i] = (1 - (1 - arr[i]) * a)
, let us see how the value changes after two such operations with constants a
and b
.
Before operations, let the value be v
.
After the first one, it becomes w = 1 - (1 - v) * a
, which is a*v + (1-a)*1
.
After the second operation, the value becomes 1 - (1 - w) * b
, which is b*w + (1-b)*1
, which in turn is b*a*v + b*(1-a)*1 + (1-b)*1
, and finally becomes (b*a)*v + (1-b*a)*1
.
(I may have mixed up +s and -s, but that hopefully does not change the whole picture.)
We can now see that the value is a linear function of the original value, so we can store the coefficients b*a
and 1-b*a
of the linear and constant terms, respectively.
The problem now is that the coefficients may grow too quickly, and will soon exceed the capacity of the storage type (int
, double
or whatever).
Now, if the problem deals with integer residues modulo some integer instead of just integers or reals, that's not an issue; otherwise, storing the coefficients soon becomes problematic.