|  | 
| 1 | 1 | package g3201_3300.s3245_alternating_groups_iii | 
| 2 | 2 | 
 | 
| 3 |  | -// #Hard #Array #Binary_Indexed_Tree #2025_02_12_Time_188_ms_(100.00%)_Space_95.34_MB_(100.00%) | 
|  | 3 | +// #Hard #Array #Binary_Indexed_Tree #2025_03_15_Time_70_ms_(100.00%)_Space_108.25_MB_(_%) | 
| 4 | 4 | 
 | 
| 5 |  | -import java.util.TreeMap | 
| 6 |  | -import kotlin.math.max | 
|  | 5 | +import java.util.BitSet | 
| 7 | 6 | 
 | 
| 8 | 7 | class Solution { | 
| 9 |  | -    // Binary Indexed Tree (BIT) class. | 
| 10 |  | -    private class BIT { | 
| 11 |  | -        var bs: IntArray = IntArray(SZ) | 
| 12 |  | - | 
| 13 |  | -        // Update BIT: add value y to index x. | 
| 14 |  | -        fun update(x: Int, y: Int) { | 
| 15 |  | -            var x = x | 
| 16 |  | -            x = OFFSET - x | 
| 17 |  | -            while (x < SZ) { | 
| 18 |  | -                bs[x] += y | 
| 19 |  | -                x += x and -x | 
| 20 |  | -            } | 
| 21 |  | -        } | 
| 22 |  | - | 
| 23 |  | -        // Query BIT: get the prefix sum up to index x. | 
| 24 |  | -        fun query(x: Int): Int { | 
| 25 |  | -            var x = x | 
| 26 |  | -            x = OFFSET - x | 
| 27 |  | -            var ans = 0 | 
| 28 |  | -            while (x > 0) { | 
| 29 |  | -                ans += bs[x] | 
| 30 |  | -                x -= x and -x | 
|  | 8 | +    fun numberOfAlternatingGroups(colors: IntArray, queries: Array<IntArray>): MutableList<Int?> { | 
|  | 9 | +        val n = colors.size | 
|  | 10 | +        val set = BitSet() | 
|  | 11 | +        val bit = BIT(n) | 
|  | 12 | +        for (i in 0..<n) { | 
|  | 13 | +            if (colors[i] == colors[getIndex(i + 1, n)]) { | 
|  | 14 | +                add(set, bit, n, i) | 
| 31 | 15 |             } | 
| 32 |  | -            return ans | 
| 33 | 16 |         } | 
| 34 |  | - | 
| 35 |  | -        // Clear BIT values starting from index x. | 
| 36 |  | -        fun clear(x: Int) { | 
| 37 |  | -            var x = x | 
| 38 |  | -            x = OFFSET - x | 
| 39 |  | -            while (x < SZ) { | 
| 40 |  | -                bs[x] = 0 | 
| 41 |  | -                x += x and -x | 
|  | 17 | +        val ans: MutableList<Int?> = ArrayList<Int?>() | 
|  | 18 | +        for (q in queries) { | 
|  | 19 | +            if (q[0] == 1) { | 
|  | 20 | +                if (set.isEmpty) { | 
|  | 21 | +                    ans.add(n) | 
|  | 22 | +                } else { | 
|  | 23 | +                    val size = q[1] | 
|  | 24 | +                    val res = bit.query(size) | 
|  | 25 | +                    ans.add(res[1] - res[0] * (size - 1)) | 
|  | 26 | +                } | 
|  | 27 | +            } else { | 
|  | 28 | +                val i = q[1] | 
|  | 29 | +                var color = colors[i] | 
|  | 30 | +                if (q[2] == color) { | 
|  | 31 | +                    continue | 
|  | 32 | +                } | 
|  | 33 | +                val pre = getIndex(i - 1, n) | 
|  | 34 | +                if (colors[pre] == color) { | 
|  | 35 | +                    remove(set, bit, n, pre) | 
|  | 36 | +                } | 
|  | 37 | +                val next = getIndex(i + 1, n) | 
|  | 38 | +                if (colors[next] == color) { | 
|  | 39 | +                    remove(set, bit, n, i) | 
|  | 40 | +                } | 
|  | 41 | +                colors[i] = colors[i] xor 1 | 
|  | 42 | +                color = colors[i] | 
|  | 43 | +                if (colors[pre] == color) { | 
|  | 44 | +                    add(set, bit, n, pre) | 
|  | 45 | +                } | 
|  | 46 | +                if (colors[next] == color) { | 
|  | 47 | +                    add(set, bit, n, i) | 
|  | 48 | +                } | 
| 42 | 49 |             } | 
| 43 | 50 |         } | 
|  | 51 | +        return ans | 
| 44 | 52 |     } | 
| 45 | 53 | 
 | 
| 46 |  | -    // --- BIT wrapper methods --- | 
| 47 |  | -    // Updates both BITs for a given group length. | 
| 48 |  | -    private fun edt(x: Int, y: Int) { | 
| 49 |  | -        // Second BIT is updated with x * y. | 
| 50 |  | -        BITS[1].update(x, x * y) | 
| 51 |  | -        // First BIT is updated with y. | 
| 52 |  | -        BITS[0].update(x, y) | 
| 53 |  | -    } | 
| 54 |  | - | 
| 55 |  | -    // Combines BIT queries to get the result for a given x. | 
| 56 |  | -    private fun qry(x: Int): Int { | 
| 57 |  | -        return BITS[1].query(x) + (1 - x) * BITS[0].query(x) | 
| 58 |  | -    } | 
| 59 |  | - | 
| 60 |  | -    // Returns the length of a group from index x to y. | 
| 61 |  | -    private fun len(x: Int, y: Int): Int { | 
| 62 |  | -        return y - x + 1 | 
| 63 |  | -    } | 
| 64 |  | - | 
| 65 |  | -    // --- Group operations --- | 
| 66 |  | -    // Removes a group (block) by updating BIT with a negative value. | 
| 67 |  | -    private fun removeGroup(start: Int, end: Int) { | 
| 68 |  | -        edt(len(start, end), -1) | 
| 69 |  | -    } | 
| 70 |  | - | 
| 71 |  | -    // Adds a group (block) by updating BIT with a positive value. | 
| 72 |  | -    private fun addGroup(start: Int, end: Int) { | 
| 73 |  | -        edt(len(start, end), 1) | 
| 74 |  | -    } | 
| 75 |  | - | 
| 76 |  | -    // Initializes the alternating groups using the colors array. | 
| 77 |  | -    private fun initializeGroups(colors: IntArray, groups: TreeMap<Int, Int>) { | 
| 78 |  | -        val n = colors.size | 
| 79 |  | -        var i = 0 | 
| 80 |  | -        while (i < n) { | 
| 81 |  | -            var r = i | 
| 82 |  | -            // Determine the end of the current alternating group. | 
| 83 |  | -            while (r < n && (colors[r] + colors[i] + r + i) % 2 == 0) { | 
| 84 |  | -                ++r | 
| 85 |  | -            } | 
| 86 |  | -            // The group spans from index i to r-1. | 
| 87 |  | -            groups.put(i, r - 1) | 
| 88 |  | -            // Update BITs with the length of this group. | 
| 89 |  | -            edt(r - i, 1) | 
| 90 |  | -            // Skip to the end of the current group. | 
| 91 |  | -            i = r - 1 | 
| 92 |  | -            ++i | 
|  | 54 | +    private fun add(set: BitSet, bit: BIT, n: Int, i: Int) { | 
|  | 55 | +        if (set.isEmpty) { | 
|  | 56 | +            bit.update(n, 1) | 
|  | 57 | +        } else { | 
|  | 58 | +            update(set, bit, n, i, 1) | 
| 93 | 59 |         } | 
|  | 60 | +        set.set(i) | 
| 94 | 61 |     } | 
| 95 | 62 | 
 | 
| 96 |  | -    // Processes a type 1 query: returns the number of alternating groups | 
| 97 |  | -    // of at least the given size. | 
| 98 |  | -    private fun processQueryType1(colors: IntArray, groups: TreeMap<Int, Int>, groupSize: Int): Int { | 
| 99 |  | -        var ans = qry(groupSize) | 
| 100 |  | -        val firstGroup = groups.firstEntry() | 
| 101 |  | -        val lastGroup = groups.lastEntry() | 
| 102 |  | -        // If there is more than one group and the first and last colors differ, | 
| 103 |  | -        // adjust the answer by "merging" the groups at the boundaries. | 
| 104 |  | -        if (firstGroup !== lastGroup && colors[0] != colors[colors.size - 1]) { | 
| 105 |  | -            val leftLen = len(firstGroup.key!!, firstGroup.value!!) | 
| 106 |  | -            val rightLen = len(lastGroup.key!!, lastGroup.value!!) | 
| 107 |  | -            ans = (ans - max((leftLen - groupSize + 1).toDouble(), 0.0)).toInt() | 
| 108 |  | -            ans = (ans - max((rightLen - groupSize + 1).toDouble(), 0.0)).toInt() | 
| 109 |  | -            ans = (ans + max((leftLen + rightLen - groupSize + 1).toDouble(), 0.0)).toInt() | 
|  | 63 | +    private fun remove(set: BitSet, bit: BIT, n: Int, i: Int) { | 
|  | 64 | +        set.clear(i) | 
|  | 65 | +        if (set.isEmpty) { | 
|  | 66 | +            bit.update(n, -1) | 
|  | 67 | +        } else { | 
|  | 68 | +            update(set, bit, n, i, -1) | 
| 110 | 69 |         } | 
| 111 |  | -        return ans | 
| 112 | 70 |     } | 
| 113 | 71 | 
 | 
| 114 |  | -    // Processes a type 2 query: updates the color at index x and adjusts groups. | 
| 115 |  | -    private fun processQueryType2( | 
| 116 |  | -        colors: IntArray, | 
| 117 |  | -        groups: TreeMap<Int, Int>, | 
| 118 |  | -        x: Int, | 
| 119 |  | -        newColor: Int, | 
| 120 |  | -    ) { | 
| 121 |  | -        if (colors[x] == newColor) { | 
| 122 |  | -            return | 
|  | 72 | +    private fun update(set: BitSet, bit: BIT, n: Int, i: Int, v: Int) { | 
|  | 73 | +        var pre = set.previousSetBit(i) | 
|  | 74 | +        if (pre == -1) { | 
|  | 75 | +            pre = set.previousSetBit(n) | 
| 123 | 76 |         } | 
| 124 |  | -        // Update the color at index x. | 
| 125 |  | -        colors[x] = newColor | 
| 126 |  | -        // Find the group (block) that contains index x. | 
| 127 |  | -        var it = groups.floorEntry(x) | 
| 128 |  | -        val l: Int = it!!.key!! | 
| 129 |  | -        val r: Int = it.value!! | 
| 130 |  | -        // Remove the old group from BIT and map. | 
| 131 |  | -        removeGroup(l, r) | 
| 132 |  | -        groups.remove(l) | 
| 133 |  | -        var ml = x | 
| 134 |  | -        var mr = x | 
| 135 |  | -        // Process the left side of index x. | 
| 136 |  | -        if (l != x) { | 
| 137 |  | -            groups.put(l, x - 1) | 
| 138 |  | -            addGroup(l, x - 1) | 
| 139 |  | -        } else { | 
| 140 |  | -            if (x > 0 && colors[x] != colors[x - 1]) { | 
| 141 |  | -                it = groups.floorEntry(x - 1) | 
| 142 |  | -                if (it != null) { | 
| 143 |  | -                    ml = it.key!! | 
| 144 |  | -                    removeGroup(it.key!!, it.value!!) | 
| 145 |  | -                    groups.remove(it.key) | 
| 146 |  | -                } | 
| 147 |  | -            } | 
|  | 77 | +        var next = set.nextSetBit(i) | 
|  | 78 | +        if (next == -1) { | 
|  | 79 | +            next = set.nextSetBit(0) | 
| 148 | 80 |         } | 
| 149 |  | -        // Process the right side of index x. | 
| 150 |  | -        if (r != x) { | 
| 151 |  | -            groups.put(x + 1, r) | 
| 152 |  | -            addGroup(x + 1, r) | 
| 153 |  | -        } else { | 
| 154 |  | -            if (x + 1 < colors.size && colors[x + 1] != colors[x]) { | 
| 155 |  | -                it = groups.ceilingEntry(x + 1) | 
| 156 |  | -                if (it != null) { | 
| 157 |  | -                    mr = it.value!! | 
| 158 |  | -                    removeGroup(it.key!!, it.value!!) | 
| 159 |  | -                    groups.remove(it.key) | 
| 160 |  | -                } | 
| 161 |  | -            } | 
| 162 |  | -        } | 
| 163 |  | - | 
| 164 |  | -        // Merge the affected groups into one new group. | 
| 165 |  | -        groups.put(ml, mr) | 
| 166 |  | -        addGroup(ml, mr) | 
|  | 81 | +        bit.update(getIndex(next - pre + n - 1, n) + 1, -v) | 
|  | 82 | +        bit.update(getIndex(i - pre, n), v) | 
|  | 83 | +        bit.update(getIndex(next - i, n), v) | 
| 167 | 84 |     } | 
| 168 | 85 | 
 | 
| 169 |  | -    // Clears both BITs. This is done after processing all queries. | 
| 170 |  | -    private fun clearAllBITs(n: Int) { | 
| 171 |  | -        for (i in 0..n + 2) { | 
| 172 |  | -            BITS[0].clear(i) | 
| 173 |  | -            BITS[1].clear(i) | 
| 174 |  | -        } | 
|  | 86 | +    private fun getIndex(index: Int, mod: Int): Int { | 
|  | 87 | +        val result = if (index >= mod) index - mod else index | 
|  | 88 | +        return if (index < 0) index + mod else result | 
| 175 | 89 |     } | 
| 176 | 90 | 
 | 
| 177 |  | -    // Main function to handle queries on alternating groups. | 
| 178 |  | -    fun numberOfAlternatingGroups(colors: IntArray, queries: Array<IntArray>): MutableList<Int> { | 
| 179 |  | -        val groups = TreeMap<Int, Int>() | 
| 180 |  | -        val n = colors.size | 
| 181 |  | -        val results: MutableList<Int> = ArrayList<Int>() | 
| 182 |  | -        // Initialize alternating groups. | 
| 183 |  | -        initializeGroups(colors, groups) | 
| 184 |  | -        // Process each query. | 
| 185 |  | -        for (query in queries) { | 
| 186 |  | -            if (query[0] == 1) { | 
| 187 |  | -                // Type 1 query: count alternating groups of at least a given size. | 
| 188 |  | -                val groupSize = query[1] | 
| 189 |  | -                val ans = processQueryType1(colors, groups, groupSize) | 
| 190 |  | -                results.add(ans) | 
| 191 |  | -            } else { | 
| 192 |  | -                // Type 2 query: update the color at a given index. | 
| 193 |  | -                val index = query[1] | 
| 194 |  | -                val newColor = query[2] | 
| 195 |  | -                processQueryType2(colors, groups, index, newColor) | 
|  | 91 | +    private class BIT(n: Int) { | 
|  | 92 | +        var n: Int = n + 1 | 
|  | 93 | +        var tree1: IntArray = IntArray(n + 1) | 
|  | 94 | +        var tree2: IntArray = IntArray(n + 1) | 
|  | 95 | + | 
|  | 96 | +        fun update(size: Int, v: Int) { | 
|  | 97 | +            var i = size | 
|  | 98 | +            while (i > 0) { | 
|  | 99 | +                tree1[i] += v | 
|  | 100 | +                tree2[i] += v * size | 
|  | 101 | +                i -= i and -i | 
| 196 | 102 |             } | 
| 197 | 103 |         } | 
| 198 |  | -        // Clear BITs after processing all queries. | 
| 199 |  | -        clearAllBITs(n) | 
| 200 |  | -        return results | 
| 201 |  | -    } | 
| 202 | 104 | 
 | 
| 203 |  | -    companion object { | 
| 204 |  | -        private const val SZ = 63333 | 
| 205 |  | -        private const val OFFSET: Int = SZ - 10 | 
| 206 |  | -        private val BITS = arrayOf<BIT>(BIT(), BIT()) | 
|  | 105 | +        fun query(size: Int): IntArray { | 
|  | 106 | +            var count = 0 | 
|  | 107 | +            var sum = 0 | 
|  | 108 | +            var i = size | 
|  | 109 | +            while (i < n) { | 
|  | 110 | +                count += tree1[i] | 
|  | 111 | +                sum += tree2[i] | 
|  | 112 | +                i += i and -i | 
|  | 113 | +            } | 
|  | 114 | +            return intArrayOf(count, sum) | 
|  | 115 | +        } | 
| 207 | 116 |     } | 
| 208 | 117 | } | 
0 commit comments