Skip to main content
added array-solution using Reinderien's suggestion
Source Link

Can be used on Marc's version as well. Benchmarks with various solutions on three worst case inputs, where the above is actually the fastest despite the O(n+m) complexity (for the 64-bit version, which seems to be the default for Windows now):

CPython 3.9.0 64-bit on Windows 10 Pro 2004 64-bit:

       original  792 798 ms  795 787 ms  840 795 ms
       with_abk  789 785 ms  783 790 ms  808 807 ms
with_accumulate  574 581 ms  579 581 ms  576 596 ms
           Marc  732 736 ms  728 737 ms  726 736 ms
    optimized_1  686 698 ms  687 702 ms  687 698 ms
    optimized_2  686 696 ms  685 694 ms  679 690 ms
    optimized_3  682 692 ms  678 683 ms  677 684 ms
     Reinderien   516 ms   512 ms   511 ms

CPython 3.9.0 32-bit on Windows 10 Pro 2004 64-bit:

       original  12191200 ms  12161229 ms  11981259 ms
       with_abk  11791167 ms  11911203 ms  11821174 ms
with_accumulate   951939 ms   940937 ms   942934 ms
           Marc   930922 ms   930927 ms   932923 ms
    optimized_1   872865 ms   873868 ms   873869 ms
    optimized_2   858863 ms   856863 ms   859868 ms
    optimized_3   856851 ms   858847 ms   853842 ms
     Reinderien   979 ms   959 ms   983 ms
from timeit import repeat
from random import randint
from itertools import accumulate
from randomarray import randintarray

def original(n, queries):
    nums = [0] * (n + 1)
    for q in queries:
        nums[q[0]-1] += q[2]
        nums[q[1]] -= q[2]
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_abk(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_accumulate(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    return max(accumulate(nums))

def Marc(n, queries):
    indices = []
    for a, b, k in queries:
        indices.append((a, k))
        indices.append((b + 1, -k))
    indices.sort()
    running_sum = 0
    result = 0
    for _, k in indices:
        running_sum += k
        result = max(result, running_sum)
    return result

def optimized_1(n, queries):
    changes = []
    for a, b, k in queries:
        changes.append((a, k))
        changes.append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_2(n, queries):
    changes = []
    append = changes.append
    for a, b, k in queries:
        append((a, k))
        append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_3(n, queries):
    changes = [(a, k) for a, _, k in queries]
    changes += [(b + 1, -k) for _, b, k in queries]
    changes.sort()
    return max(accumulate(k for _, k in changes))

def Reinderien(n, queries):
    nums = array('q', [0]) * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    return max(accumulate(nums)) 


funcs = original, with_abk, with_accumulate, Marc, optimized_1, optimized_2, optimized_3, Reinderien
names = [func.__name__ for func in funcs]

def worst_case():
    n = 10**7
    m = 2 * 10**5
    queries = [sorted([randint(1, n), randint(1, n)]) + [randint(0, 10**9)]
               for _ in range(m)]
    return n, queries

# Check correctness
n, queries = worst_case()
expect = funcs[0](n, queries)
for func in funcs[1:]:
    print(func(n, queries) == expect, func.__name__)

# Benchmark
tss = [[] for _ in funcs]
for _ in range(3):
    n, queries = worst_case()
    for func, ts in zip(funcs, tss):
        t = min(repeat(lambda: func(n, queries), number=1))
        ts.append(t)
    print()
    for name, ts in zip(names, tss):
        print(name.rjust(max(map(len, names))),
              *(' %d%4d ms' % (t * 1000) for t in ts))

Can be used on Marc's version as well. Benchmarks with various solutions on three worst case inputs, where the above is actually the fastest despite the O(n+m) complexity (for the 64-bit version, which seems to be the default for Windows now):

CPython 3.9.0 64-bit on Windows 10 Pro 2004 64-bit:

       original  792 ms  795 ms  840 ms
       with_abk  789 ms  783 ms  808 ms
with_accumulate  574 ms  579 ms  576 ms
           Marc  732 ms  728 ms  726 ms
    optimized_1  686 ms  687 ms  687 ms
    optimized_2  686 ms  685 ms  679 ms
    optimized_3  682 ms  678 ms  677 ms

CPython 3.9.0 32-bit on Windows 10 Pro 2004 64-bit:

       original  1219 ms  1216 ms  1198 ms
       with_abk  1179 ms  1191 ms  1182 ms
with_accumulate   951 ms   940 ms   942 ms
           Marc   930 ms   930 ms   932 ms
    optimized_1   872 ms   873 ms   873 ms
    optimized_2   858 ms   856 ms   859 ms
    optimized_3   856 ms   858 ms   853 ms
from timeit import repeat
from itertools import accumulate
from random import randint

def original(n, queries):
    nums = [0] * (n + 1)
    for q in queries:
        nums[q[0]-1] += q[2]
        nums[q[1]] -= q[2]
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_abk(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_accumulate(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    return max(accumulate(nums))

def Marc(n, queries):
    indices = []
    for a, b, k in queries:
        indices.append((a, k))
        indices.append((b + 1, -k))
    indices.sort()
    running_sum = 0
    result = 0
    for _, k in indices:
        running_sum += k
        result = max(result, running_sum)
    return result

def optimized_1(n, queries):
    changes = []
    for a, b, k in queries:
        changes.append((a, k))
        changes.append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_2(n, queries):
    changes = []
    append = changes.append
    for a, b, k in queries:
        append((a, k))
        append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_3(n, queries):
    changes = [(a, k) for a, _, k in queries]
    changes += [(b + 1, -k) for _, b, k in queries]
    changes.sort()
    return max(accumulate(k for _, k in changes))

funcs = original, with_abk, with_accumulate, Marc, optimized_1, optimized_2, optimized_3
names = [func.__name__ for func in funcs]

def worst_case():
    n = 10**7
    m = 2 * 10**5
    queries = [sorted([randint(1, n), randint(1, n)]) + [randint(0, 10**9)]
               for _ in range(m)]
    return n, queries

# Check correctness
n, queries = worst_case()
expect = funcs[0](n, queries)
for func in funcs[1:]:
    print(func(n, queries) == expect, func.__name__)

# Benchmark
tss = [[] for _ in funcs]
for _ in range(3):
    n, queries = worst_case()
    for func, ts in zip(funcs, tss):
        t = min(repeat(lambda: func(n, queries), number=1))
        ts.append(t)
    print()
    for name, ts in zip(names, tss):
        print(name.rjust(max(map(len, names))),
              *(' %d ms' % (t * 1000) for t in ts))

Can be used on Marc's version as well. Benchmarks with various solutions on three worst case inputs:

CPython 3.9.0 64-bit on Windows 10 Pro 2004 64-bit:

       original   798 ms   787 ms   795 ms
       with_abk   785 ms   790 ms   807 ms
with_accumulate   581 ms   581 ms   596 ms
           Marc   736 ms   737 ms   736 ms
    optimized_1   698 ms   702 ms   698 ms
    optimized_2   696 ms   694 ms   690 ms
    optimized_3   692 ms   683 ms   684 ms
     Reinderien   516 ms   512 ms   511 ms

CPython 3.9.0 32-bit on Windows 10 Pro 2004 64-bit:

       original  1200 ms  1229 ms  1259 ms
       with_abk  1167 ms  1203 ms  1174 ms
with_accumulate   939 ms   937 ms   934 ms
           Marc   922 ms   927 ms   923 ms
    optimized_1   865 ms   868 ms   869 ms
    optimized_2   863 ms   863 ms   868 ms
    optimized_3   851 ms   847 ms   842 ms
     Reinderien   979 ms   959 ms   983 ms
from timeit import repeat
from random import randint
from itertools import accumulate
from array import array

def original(n, queries):
    nums = [0] * (n + 1)
    for q in queries:
        nums[q[0]-1] += q[2]
        nums[q[1]] -= q[2]
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_abk(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_accumulate(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    return max(accumulate(nums))

def Marc(n, queries):
    indices = []
    for a, b, k in queries:
        indices.append((a, k))
        indices.append((b + 1, -k))
    indices.sort()
    running_sum = 0
    result = 0
    for _, k in indices:
        running_sum += k
        result = max(result, running_sum)
    return result

def optimized_1(n, queries):
    changes = []
    for a, b, k in queries:
        changes.append((a, k))
        changes.append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_2(n, queries):
    changes = []
    append = changes.append
    for a, b, k in queries:
        append((a, k))
        append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_3(n, queries):
    changes = [(a, k) for a, _, k in queries]
    changes += [(b + 1, -k) for _, b, k in queries]
    changes.sort()
    return max(accumulate(k for _, k in changes))

def Reinderien(n, queries):
    nums = array('q', [0]) * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    return max(accumulate(nums)) 


funcs = original, with_abk, with_accumulate, Marc, optimized_1, optimized_2, optimized_3, Reinderien
names = [func.__name__ for func in funcs]

def worst_case():
    n = 10**7
    m = 2 * 10**5
    queries = [sorted([randint(1, n), randint(1, n)]) + [randint(0, 10**9)]
               for _ in range(m)]
    return n, queries

# Check correctness
n, queries = worst_case()
expect = funcs[0](n, queries)
for func in funcs[1:]:
    print(func(n, queries) == expect, func.__name__)

# Benchmark
tss = [[] for _ in funcs]
for _ in range(3):
    n, queries = worst_case()
    for func, ts in zip(funcs, tss):
        t = min(repeat(lambda: func(n, queries), number=1))
        ts.append(t)
    print()
    for name, ts in zip(names, tss):
        print(name.rjust(max(map(len, names))),
              *(' %4d ms' % (t * 1000) for t in ts))
added 32-bit Python times
Source Link

Can be used on Marc's version as well. Benchmarks with various solutions on three worst case inputs, where the above is actually the fastest despite the O(n+m) complexity (for the 64-bit version, which seems to be the default for Windows now):

CPython 3.9.0 64-bit on Windows 10 Pro 2004 64-bit:

       original  792 ms  795 ms  840 ms
       with_abk  789 ms  783 ms  808 ms
with_accumulate  574 ms  579 ms  576 ms
           Marc  732 ms  728 ms  726 ms
    optimized_1  686 ms  687 ms  687 ms
    optimized_2  686 ms  685 ms  679 ms
    optimized_3  682 ms  678 ms  677 ms

CPython 3.9.0 32-bit on Windows 10 Pro 2004 64-bit:

       original  1219 ms  1216 ms  1198 ms
       with_abk  1179 ms  1191 ms  1182 ms
with_accumulate   951 ms   940 ms   942 ms
           Marc   930 ms   930 ms   932 ms
    optimized_1   872 ms   873 ms   873 ms
    optimized_2   858 ms   856 ms   859 ms
    optimized_3   856 ms   858 ms   853 ms

Done with Python 3.9.0 64-bit on Windows 10 Pro 2004 64-bit.

Can be used on Marc's version as well. Benchmarks with various solutions on three worst case inputs, where the above is actually the fastest despite the O(n+m) complexity:

       original  792 ms  795 ms  840 ms
       with_abk  789 ms  783 ms  808 ms
with_accumulate  574 ms  579 ms  576 ms
           Marc  732 ms  728 ms  726 ms
    optimized_1  686 ms  687 ms  687 ms
    optimized_2  686 ms  685 ms  679 ms
    optimized_3  682 ms  678 ms  677 ms

Done with Python 3.9.0 64-bit on Windows 10 Pro 2004 64-bit.

Can be used on Marc's version as well. Benchmarks with various solutions on three worst case inputs, where the above is actually the fastest despite the O(n+m) complexity (for the 64-bit version, which seems to be the default for Windows now):

CPython 3.9.0 64-bit on Windows 10 Pro 2004 64-bit:

       original  792 ms  795 ms  840 ms
       with_abk  789 ms  783 ms  808 ms
with_accumulate  574 ms  579 ms  576 ms
           Marc  732 ms  728 ms  726 ms
    optimized_1  686 ms  687 ms  687 ms
    optimized_2  686 ms  685 ms  679 ms
    optimized_3  682 ms  678 ms  677 ms

CPython 3.9.0 32-bit on Windows 10 Pro 2004 64-bit:

       original  1219 ms  1216 ms  1198 ms
       with_abk  1179 ms  1191 ms  1182 ms
with_accumulate   951 ms   940 ms   942 ms
           Marc   930 ms   930 ms   932 ms
    optimized_1   872 ms   873 ms   873 ms
    optimized_2   858 ms   856 ms   859 ms
    optimized_3   856 ms   858 ms   853 ms
Show time in ms instead of s
Source Link
       original  0.78792 sms  0.78795 sms  0.78840 sms
       with_abk  0.78789 sms  0.77783 sms  0.80808 sms
with_accumulate  0.57574 sms  0.58579 sms  0.57576 sms
           Marc  0.71732 sms  0.73728 sms  0.72726 sms
    optimized_1  0.69686 sms  0.69687 sms  0.68687 sms
    optimized_2  0.68686 sms  0.68685 sms  0.68679 sms
    optimized_3  0.67682 sms  0.67678 sms  0.67677 sms
from timeit import repeat
from itertools import accumulate
from random import randint

def original(n, queries):
    nums = [0] * (n + 1)
    for q in queries:
        nums[q[0]-1] += q[2]
        nums[q[1]] -= q[2]
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_abk(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_accumulate(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    return max(accumulate(nums))

def Marc(n, queries):
    indices = []
    for a, b, k in queries:
        indices.append((a, k))
        indices.append((b + 1, -k))
    indices.sort()
    running_sum = 0
    result = 0
    for _, k in indices:
        running_sum += k
        result = max(result, running_sum)
    return result

def optimized_1(n, queries):
    changes = []
    for a, b, k in queries:
        changes.append((a, k))
        changes.append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_2(n, queries):
    changes = []
    append = changes.append
    for a, b, k in queries:
        append((a, k))
        append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_3(n, queries):
    changes = [(a, k) for a, _, k in queries]
    changes += [(b + 1, -k) for _, b, k in queries]
    changes.sort()
    return max(accumulate(k for _, k in changes))

funcs = original, with_abk, with_accumulate, Marc, optimized_1, optimized_2, optimized_3
names = [func.__name__ for func in funcs]

def worst_case():
    n = 10**7
    m = 2 * 10**5
    queries = [sorted([randint(1, n), randint(1, n)]) + [randint(0, 10**9)]
               for _ in range(m)]
    return n, queries

# Check correctness
n, queries = worst_case()
expect = funcs[0](n, queries)
for func in funcs[1:]:
    print(func(n, queries) == expect, func.__name__)

# Benchmark
tss = [[] for _ in funcs]
for _ in range(3):
    n, queries = worst_case()
    for func, ts in zip(funcs, tss):
        t = min(repeat(lambda: func(n, queries), number=1))
        ts.append(t)
    print()
    for name, ts in zip(names, tss):
        print(name.rjust(max(map(len, names))),
              *(' %.2f%d s'ms' % (t * 1000) for t in ts))
       original  0.78 s  0.78 s  0.78 s
       with_abk  0.78 s  0.77 s  0.80 s
with_accumulate  0.57 s  0.58 s  0.57 s
           Marc  0.71 s  0.73 s  0.72 s
    optimized_1  0.69 s  0.69 s  0.68 s
    optimized_2  0.68 s  0.68 s  0.68 s
    optimized_3  0.67 s  0.67 s  0.67 s
from timeit import repeat
from itertools import accumulate
from random import randint

def original(n, queries):
    nums = [0] * (n + 1)
    for q in queries:
        nums[q[0]-1] += q[2]
        nums[q[1]] -= q[2]
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_abk(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_accumulate(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    return max(accumulate(nums))

def Marc(n, queries):
    indices = []
    for a, b, k in queries:
        indices.append((a, k))
        indices.append((b + 1, -k))
    indices.sort()
    running_sum = 0
    result = 0
    for _, k in indices:
        running_sum += k
        result = max(result, running_sum)
    return result

def optimized_1(n, queries):
    changes = []
    for a, b, k in queries:
        changes.append((a, k))
        changes.append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_2(n, queries):
    changes = []
    append = changes.append
    for a, b, k in queries:
        append((a, k))
        append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_3(n, queries):
    changes = [(a, k) for a, _, k in queries]
    changes += [(b + 1, -k) for _, b, k in queries]
    changes.sort()
    return max(accumulate(k for _, k in changes))

funcs = original, with_abk, with_accumulate, Marc, optimized_1, optimized_2, optimized_3
names = [func.__name__ for func in funcs]

def worst_case():
    n = 10**7
    m = 2 * 10**5
    queries = [sorted([randint(1, n), randint(1, n)]) + [randint(0, 10**9)]
               for _ in range(m)]
    return n, queries

# Check correctness
n, queries = worst_case()
expect = funcs[0](n, queries)
for func in funcs[1:]:
    print(func(n, queries) == expect, func.__name__)

# Benchmark
tss = [[] for _ in funcs]
for _ in range(3):
    n, queries = worst_case()
    for func, ts in zip(funcs, tss):
        t = min(repeat(lambda: func(n, queries), number=1))
        ts.append(t)
    print()
    for name, ts in zip(names, tss):
        print(name.rjust(max(map(len, names))),
              *(' %.2f s' % t for t in ts))
       original  792 ms  795 ms  840 ms
       with_abk  789 ms  783 ms  808 ms
with_accumulate  574 ms  579 ms  576 ms
           Marc  732 ms  728 ms  726 ms
    optimized_1  686 ms  687 ms  687 ms
    optimized_2  686 ms  685 ms  679 ms
    optimized_3  682 ms  678 ms  677 ms
from timeit import repeat
from itertools import accumulate
from random import randint

def original(n, queries):
    nums = [0] * (n + 1)
    for q in queries:
        nums[q[0]-1] += q[2]
        nums[q[1]] -= q[2]
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_abk(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    current = 0
    max = 0
    for i in nums:
        current += i
        if current > max: max = current
    return max

def with_accumulate(n, queries):
    nums = [0] * (n + 1)
    for a, b, k in queries:
        nums[a - 1] += k
        nums[b] -= k
    return max(accumulate(nums))

def Marc(n, queries):
    indices = []
    for a, b, k in queries:
        indices.append((a, k))
        indices.append((b + 1, -k))
    indices.sort()
    running_sum = 0
    result = 0
    for _, k in indices:
        running_sum += k
        result = max(result, running_sum)
    return result

def optimized_1(n, queries):
    changes = []
    for a, b, k in queries:
        changes.append((a, k))
        changes.append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_2(n, queries):
    changes = []
    append = changes.append
    for a, b, k in queries:
        append((a, k))
        append((b + 1, -k))
    changes.sort()
    return max(accumulate(k for _, k in changes))

def optimized_3(n, queries):
    changes = [(a, k) for a, _, k in queries]
    changes += [(b + 1, -k) for _, b, k in queries]
    changes.sort()
    return max(accumulate(k for _, k in changes))

funcs = original, with_abk, with_accumulate, Marc, optimized_1, optimized_2, optimized_3
names = [func.__name__ for func in funcs]

def worst_case():
    n = 10**7
    m = 2 * 10**5
    queries = [sorted([randint(1, n), randint(1, n)]) + [randint(0, 10**9)]
               for _ in range(m)]
    return n, queries

# Check correctness
n, queries = worst_case()
expect = funcs[0](n, queries)
for func in funcs[1:]:
    print(func(n, queries) == expect, func.__name__)

# Benchmark
tss = [[] for _ in funcs]
for _ in range(3):
    n, queries = worst_case()
    for func, ts in zip(funcs, tss):
        t = min(repeat(lambda: func(n, queries), number=1))
        ts.append(t)
    print()
    for name, ts in zip(names, tss):
        print(name.rjust(max(map(len, names))),
              *(' %d ms' % (t * 1000) for t in ts))
more randomized
Source Link
Loading
fix test case mistake
Source Link
Loading
added 1444 characters in body
Source Link
Loading
Source Link
Loading