// 🐋💭
import Foundation

func readInts() -> [Int] {
    return readLine()!.split(separator: " ").map { Int($0)! }
}

func readInt() -> Int {
    return Int(readLine()!)!
}

func readDoubles () -> [Double] {
    return readLine()!.split(separator: " ").map { Double($0)! }
}

func readDouble() -> Double{
    return Double(readLine()!)!
}
///最大公約数を返す
func gcd(_ m:Int, _ n:Int) -> Int {
    if n == 0 {return m}
    return gcd(n, m % n)
}
///最小公倍数を返す
func lcm(_ m:Int, _ n:Int) -> Int {
    return m * n / gcd(m, n)
}

///素数判定(試し割り)
func isPrime(_ x:Int) -> Bool {
    if x <= 1 || x % 2 == 0 { return false }
    if x == 2 { return true }
    for i in stride(from: 3, to: Int(ceil(sqrt(Double(x)))), by: 2){
        if x % i == 0 {
            return false
        }
    }
    return true
}

//https://program-life.com/669
///実行速度の計測
func exectionTime(action: () -> Void) {
    let startDate = Date()
    action()
    let endDate = Date()
    print(endDate.timeIntervalSince(startDate))
}

// https://melheaven.hatenadiary.jp/entry/atcoder144
///約数の配列を返す
func makeDivisors(_ n:Int) -> [Int] {
    var divisors = [Int]()
    for i in 1..<Int(pow(Double(n), 0.5)) + 1 {
        if n % i == 0{
            divisors.append(i + n / i)
        }
    }

    return divisors
}

//https://riptutorial.com/ja/swift/example/14738/インデックスを知らずに配列から要素を削除する
extension Array where Element: Equatable {

    mutating func remove(_ element: Element) {
        _ = index(of: element).flatMap {
            self.remove(at: $0)
        }
    }
}

func binarySearch(rifgt:Int, key:Int, ary:[String]) -> Int{
    var left = 0
    var right = rifgt

    while abs(right - left) > 1 {
        let mid = left + (right - left) / 2

        if Int(ary[mid])! < key {
            left = mid

        } else {
            right = mid
        }
    }
    return left
}

func arrangement (prev:String, ary:[String], returnVal:[String]) -> [String] {
    var returnVal = returnVal
    if ary.count == 0 {
        returnVal.append(prev)
        return returnVal
    }

    for s in ary {
        let wprev = prev + s
        var wary = ary
        wary.remove(s)
        returnVal = arrangement(prev: wprev, ary: wary, returnVal: returnVal)
    }
    return returnVal
}

func main() {
    let s = readLine()!.map { String($0) }
    var zeroOne = 0
    var oneZero = 0
    var nextColor = "0"

    for i in 0..<s.count {
        if s[i] == nextColor {
            zeroOne += 1
        } else {
            oneZero += 1
        }
        nextColor = nextColor == "0" ? "1": "0"
    }
    print(min(zeroOne, oneZero))
}
main()


let S = readLine()!.characters.map{String($0)}
var (tile1, tile2) = (0, 0)
for i in 0..<S.count {
    if i % 2 > 0 {
        S[i]=="0" ? tile1++ : tile2++
    } else {
        S[i]=="1" ? tile1++ : tile2++
    }
}
print(min(tile1, tile2))


var arr=readLine()!
var ba:[Bool] = arr.characters.map { if(String($0) == "0") { return false } else { return true }  }

var prev = ba[0]
var j: Int = 0
var c: Int = 0

for j = 1; j < ba.count; j+=1 {
  if( prev == ba[j] ) {
    ba[j] = !ba[j]
    c += 1
  }
  prev = ba[j]
}

print( c )


var a=[0,0];for i in 0..<2{for j in 0..<(readLine()!.characters.map{String($0)}).count{if j%2^i != Int(S[j]){a[i]++}}};print(min(a[0],a[1]))


var S=readLine()!.characters.map{String($0)},a=[0,0];for i in 0..<2{for j in 0..<S.count{if"\(j%2^i)"!=S[j]{a[i]++}}};print(min(a[0],a[1]))


//var S=readLine()!.characters.map{String($0)},a=[0,0];for i in 0..<2{for(j,c)in S.enumerate(){if j%2^i != Int(c){a[i]++}}};print(min(a[0],a[1]))
var S=readLine()!.characters.map{String($0)},a=[0,0];for i in 0..<2{for j in S{if j%2^i != Int(S[j]){a[i]++}}};print(min(a[0],a[1]))


function main(input) {
  const s = input.trim();
  let pattern1 = 0;
  let pattern2 = 0;
  for (let i = 0; i < s.length; i++) {
    const char = s[i];
    if (i%2 === 0 && s[i] === "0") {
      pattern2++;
    } else if (i%2 === 0 && s[i] === "1") {
      pattern1++;
    } else if (i%2 === 1 && s[i] === "0") {
      pattern1++;
    } else if (i%2 === 1 && s[i] === "1") {
      pattern2++;
    }
  }
  const result = Math.min(pattern1, pattern2);
  console.log(result);
}
main(require("fs").readFileSync("/dev/stdin", "utf8"));


function Main(raw: string) {
  const input = raw.split("\n");
  const S = input[0];
  const N = S.length;
  let min = 100000;
  for (let start = 0; start < 2; start++) {
    let count = 0;
    for (let i = 0; i < N; i++) {
      let expected = (start + i) % 2 === 0 ? "0" : "1";
      if (S[i] !== expected) {
        count++;
      }
    }
    min = Math.min(min, count);
  }
  //出力
  console.log(min);
}

//DO NOT EDIT
Main(require("fs").readFileSync("/dev/stdin", "utf8"));


const input=require('fs').readFileSync('/dev/stdin','utf8').split(/[\s]/);let input_i=-1;const tn=():number=>{input_i++;return Number(input[input_i])};const tnn=(n:number):number[]=>{const a=[];for(let i=0;i<n;i++){a.push(tn())}return a};const ts=():string=>{input_i++;return String(input[input_i])};const tss=(n:number):string[]=>{const a=[];for(let i=0;i<n;i++){a.push(ts())}return a};const to=(...arg)=>{console.log(...arg)};const abs=(a)=>{return Math.abs(a)};const min=(...a)=>{return Math.min(...a)};const max=(...a)=>{return Math.max(...a)};const floor=(a)=>{return Math.floor(a)};const asc=(ar:number[])=>{const t=[...ar];return ar.sort((a,b)=>{return a-b})};const desc=(ar:number[])=>{const t=[...ar];return t.sort((a,b)=>{return b-a})};const sw=(a,arr:any[],op:'=='|'>='|'>'|'<='|'<'='==')=>{for(let i=0;i<arr.length-1;i+=2){if(eval(a+op+arr[i])){return arr[i+1]}}return arr[arr.length-1]};const near=(n:number,arr:number[]):number=>{let ne=arr[0],ni=0;for(let i=1;i<arr.length;i++){if(Math.abs(n-ne)>Math.abs(n-arr[i])){ne=arr[i];ni=i}}return ni};const deduplicate=(a:any[]):any[]=>{return Array.from(new Set(a))};const gcd=(x,y)=>{while(y){[x,y]=[y,x%y]}return x};
const main=()=>{
  let s=ts(),c=0
  for(let i=0;i<s.length;i++){
    if(i%2===0&&s[i]==='0'){
      c++
    }else if(i%2===1&&s[i]==='1'){
      c++
    }
  }
  if(c>=s.length/2){
    to(s.length-c)
  }else{
    to(c)
  }
}
main()


let inp = require('fs').readFileSync('/dev/stdin', 'utf8').replace(/\n/g, '').trim();

if (inp.length % 2 === 1) {
  const last = inp.slice(-1) === '0' ? '1' : '0';
  inp += last;
}

const grps = inp.match(/.{2}/g);

const zo = grps.filter(v => v === '01');
const oz = grps.filter(v => v === '10');
const zz = grps.filter(v => v === '00');
const oo = grps.filter(v => v === '11');

const minPtn = Math.min(zo.length, oz.length);
const lastPtn = grps[grps.length - 1];

const mnsone = (minPtn === zo.length && lastPtn === '01') || (minPtn === oz.length && lastPtn === '10')

console.log(
Math.min(zo.length, oz.length) * 2 + zz.length + oo.length + (mnsone ? -1 : 0)
)


let inp = require('fs').readFileSync('/dev/stdin', 'utf8');

if (inp.length % 2 === 1) {
	inp = inp + (inp.slice(-1) === '0' ? '1' : '0')
}

const grps = inp.match(/.{2}/g);

const zo = grps.filter(v => v === '01');
const oz = grps.filter(v => v === '10');
const zz = grps.filter(v => v === '00');
const oo = grps.filter(v => v === '11');

console.log(Math.min(zo.length, oz.length) * 2 + zz.length + oo.length)


"use strict";
function main(arg) {
  const input = String(arg);
  // 文字列 "0101・・・"　との差
  // 文字列 "1010・・・"　との差
  let unMatchCont0 = 0;
  let unMatchCont1 = 0;
  input.split("").forEach((char, index) => {
    if (index % 2 === 0) {
      if (char != "0") {
        unMatchCont0++;
      } else {
        unMatchCont1++;
      }
    } else {
      if (char != "1") {
        unMatchCont0++;
      } else {
        unMatchCont1++;
      }
    }
  });
  console.log(Math.min(unMatchCont0, unMatchCont1));
}
main(require("fs").readFileSync("/dev/stdin", "utf8"));


S = input()
N = len(S)

num = [0] * 2019
num[0] = 1
now, ans = 0, 0
_10 = 1

for i in range(N-1, -1, -1):
    now = (now + int(S[i]) * _10) % 2019
    _10 *= 10
    _10 %= 2019
    ans += num[now]
    num[now] += 1
print(ans)


s=input()
a=[0]*2019
a[0]+=1
pot=1
v=0
ans=0
for i in range(len(s)-1,-1,-1):
	digit=int(s[i])
	v=(v+digit*pot)%2019
	pot=(pot*10)%2019
	ans+=a[v]
	a[v]+=1

print(ans)


S = input()
s_reverse = S[::-1]

dp = [0]*2019
now_sum = 0
ten_times = 1

for ch in s_reverse:
    now_sum = (now_sum + int(ch)*ten_times) % 2019
    ten_times = (ten_times * 10) % 2019
    dp[now_sum] += 1

ans = dp[0]
for i_dp in dp:
    ans += i_dp*(i_dp-1)//2

print(ans)


S = input()
count = 0
for i in range(len(S)-3):
  for j in range(i+4, len(S)+1): # |2019| = 4
    s = int(S[i:j])
    if s % 2019 == 0:
      count += 1
print(count)


s = input()
ln = len(s)
d = [int(i) for i in s]
count = 0
for i in range(ln - 3):
    total = sum(d[i:i+3])
    for j in range(i + 3, ln):
        total += d[j]
        if total % 3 == 0 and int(s[i:j+1]) % 673 == 0:
            # print(i, j, int(s[i:j+1]))
            count += 1
print(count)


s = input()
ln = len(s)
d = [int(i) for i in s]
count = 0
for i in range(ln - 3):
    total = sum(d[i:i+3])
    for j in range(i + 3, ln):
        total += d[j]
        if total % 3 == 0 and int(s[i:j+1]) % 673 == 0:
            print(i, j, int(s[i:j+1]))
            count += 1
print(count)


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.InputMismatchException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author KharYusuf
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        FastReader in = new FastReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        DMultipleOf2019 solver = new DMultipleOf2019();
        solver.solve(1, in, out);
        out.close();
    }

    static class DMultipleOf2019 {
        public void solve(int testNumber, FastReader s, PrintWriter w) {
            char[] c = s.next().toCharArray();
            int n = c.length;
            int[] d = new int[n + 1];
            int cur = 1;
            long ans = 0;
            int[] f = new int[2019];
            f[0]++;
            for (int i = n - 1; i >= 0; i--) {
                d[i] = (c[i] - '0') * cur + d[i + 1];
                cur *= 10;
                d[i] %= 2019;
                cur %= 2019;
                ans += f[d[i]];
                f[d[i]]++;
            }
            w.println(ans);
        }

    }

    static class FastReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;
        private FastReader.SpaceCharFilter filter;

        public FastReader(InputStream stream) {
            this.stream = stream;
        }

        public int read() {

            if (numChars == -1)
                throw new InputMismatchException();

            if (curChar >= numChars) {

                curChar = 0;

                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }

                if (numChars <= 0)
                    return -1;
            }

            return buf[curChar++];
        }

        public String next() {

            int c = read();

            while (isSpaceChar(c))
                c = read();

            StringBuilder res = new StringBuilder();

            do {
                res.appendCodePoint(c);
                c = read();
            }
            while (!isSpaceChar(c));

            return res.toString();
        }

        public boolean isSpaceChar(int c) {

            if (filter != null)
                return filter.isSpaceChar(c);

            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        public interface SpaceCharFilter {
            public boolean isSpaceChar(int ch);

        }

    }
}


/**
 * @author derrick20
 */
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        FastScanner sc = new FastScanner();
        PrintWriter out = new PrintWriter(System.out);

        int P = 2019;
        char[] str = sc.next().toCharArray();
        int N = str.length;
        int[] digits = new int[N + 1];
        for (int i = 0; i < N; i++) {
            digits[i + 1] = Character.getNumericValue(str[i]);
        }

        if (P == 2 || P == 5) {
            // This means that our assumption of subtracting two
            // suffixes and dividing by 10^blah isn't always equivalent
            // to if we didn't divide by 10^blah. So, our DP doesn't work!

            long ans = 0;
            // LUCKILY, these cases are easy to handle without DP.
            for (int len = 1; len <= N; len++) {
                if (digits[len] % P == 0) {
                    // we can go from this position and end anywhere
                    // below us, at each spot along our length!
                    ans += len;
                }
            }
            out.println(ans);
        }
        else {
            int[] residueDP = new int[P];
            // store number of SUFFIXES with a given residue.
            // We can definitely enumerate all N of those, and we leverage
            // the fact that we can SUBTRACT suffixes in a way that
            // allows us to generate all valid substrings! This will be called
            // the substring from suffix trick!
            residueDP[0] = 1; // we can always build on top of the empty suffix!
            // Now we create a running suffix
            int suffix = 0;
            int shift = 1;
            long ans = 0;
            for (int pos = N; pos >= 1; pos--) {
                suffix = (suffix + shift * digits[pos]) % P;
                ans += residueDP[suffix]; // build off ALL old ones, subtracting to get 0!
                residueDP[suffix]++; // update it!
                shift = (shift * 10) % P;
//                System.out.println(Arrays.toString(residueDP));
            }
            out.println(ans);
        }

        out.close();
    }

    static class FastScanner {
        private int BS = 1<<16;
        private char NC = (char)0;
        private byte[] buf = new byte[BS];
        private int bId = 0, size = 0;
        private char c = NC;
        private double cnt = 1;
        private BufferedInputStream in;

        public FastScanner() {
            in = new BufferedInputStream(System.in, BS);
        }

        public FastScanner(String s) {
            try {
                in = new BufferedInputStream(new FileInputStream(new File(s)), BS);
            }
            catch (Exception e) {
                in = new BufferedInputStream(System.in, BS);
            }
        }

        private char getChar(){
            while(bId==size) {
                try {
                    size = in.read(buf);
                }catch(Exception e) {
                    return NC;
                }
                if(size==-1)return NC;
                bId=0;
            }
            return (char)buf[bId++];
        }

        public int nextInt() {
            return (int)nextLong();
        }

        public int[] nextInts(int N) {
            int[] res = new int[N];
            for (int i = 0; i < N; i++) {
                res[i] = (int) nextLong();
            }
            return res;
        }

        public long[] nextLongs(int N) {
            long[] res = new long[N];
            for (int i = 0; i < N; i++) {
                res[i] = nextLong();
            }
            return res;
        }

        public long nextLong() {
            cnt=1;
            boolean neg = false;
            if(c==NC)c=getChar();
            for(;(c<'0' || c>'9'); c = getChar()) {
                if(c=='-')neg=true;
            }
            long res = 0;
            for(; c>='0' && c <='9'; c=getChar()) {
                res = (res<<3)+(res<<1)+c-'0';
                cnt*=10;
            }
            return neg?-res:res;
        }

        public double nextDouble() {
            double cur = nextLong();
            return c!='.' ? cur:cur+nextLong()/cnt;
        }

        public String next() {
            StringBuilder res = new StringBuilder();
            while(c<=32)c=getChar();
            while(c>32) {
                res.append(c);
                c=getChar();
            }
            return res.toString();
        }

        public String nextLine() {
            StringBuilder res = new StringBuilder();
            while(c<=32)c=getChar();
            while(c!='\n') {
                res.append(c);
                c=getChar();
            }
            return res.toString();
        }

        public boolean hasNext() {
            if(c>32)return true;
            while(true) {
                c=getChar();
                if(c==NC)return false;
                else if(c>32)return true;
            }
        }
    }
}


import java.io.*;
import java.util.*;

public class Main implements Runnable {
	boolean judge = true;
	FastReader scn;
	PrintWriter out;
	String INPUT = "";

	void solve() {
		char[] arr = scn.next().toCharArray();
		int[] cnt = new int[2019], mul = new int[arr.length + 1];
		mul[arr.length] = 1;
		int num = 0;
		for(int i = 0; i < arr.length; i++) {
			num = (10 * num + arr[i] - '0') % 2019;
			mul[arr.length - i - 1] = (10 * mul[arr.length - i]) % 2019;
		}
		cnt[num]++;
		for(int i = 0; i < arr.length; i++) {
			num = (num - mul[i] * (arr[i] - '0') % 2019 + 2019) % 2019;
			cnt[num]++;
		}

		long ans = 0;
		for(int a : cnt) {
			ans += a * 1L * (a - 1) / 2;
		}
		out.println(ans);
	}

	public void run() {
		long time = System.currentTimeMillis();
		boolean oj = System.getProperty("ONLINE_JUDGE") != null || judge;
		out = new PrintWriter(System.out);
		scn = new FastReader(oj);
		solve();
		out.flush();
		if (!oj) {
			System.out.println(Arrays.deepToString(new Object[] { System.currentTimeMillis() - time + " ms" }));
		}
	}

	public static void main(String[] args) {
		new Thread(null, new Main(), "Main", 1 << 28).start();
	}

	class FastReader {
		InputStream is;

		public FastReader(boolean onlineJudge) {
			is = onlineJudge ? System.in : new ByteArrayInputStream(INPUT.getBytes());
		}

		byte[] inbuf = new byte[1024];
		public int lenbuf = 0, ptrbuf = 0;

		int readByte() {
			if (lenbuf == -1)
				throw new InputMismatchException();
			if (ptrbuf >= lenbuf) {
				ptrbuf = 0;
				try {
					lenbuf = is.read(inbuf);
				} catch (IOException e) {
					throw new InputMismatchException();
				}
				if (lenbuf <= 0)
					return -1;
			}
			return inbuf[ptrbuf++];
		}

		boolean isSpaceChar(int c) {
			return !(c >= 33 && c <= 126);
		}

		int skip() {
			int b;
			while ((b = readByte()) != -1 && isSpaceChar(b))
				;
			return b;
		}

		double nextDouble() {
			return Double.parseDouble(next());
		}

		char nextChar() {
			return (char) skip();
		}

		String next() {
			int b = skip();
			StringBuilder sb = new StringBuilder();
			while (!(isSpaceChar(b))) { // when nextLine, (isSpaceChar(b) && b != ' ')
				sb.appendCodePoint(b);
				b = readByte();
			}
			return sb.toString();
		}

		String nextLine() {
			int b = skip();
			StringBuilder sb = new StringBuilder();
			while ((!isSpaceChar(b) || b == ' ')) { // when nextLine, (isSpaceChar(b) && b != ' ')
				sb.appendCodePoint(b);
				b = readByte();
			}
			return sb.toString();
		}

		char[] next(int n) {
			char[] buf = new char[n];
			int b = skip(), p = 0;
			while (p < n && !(isSpaceChar(b))) {
				buf[p++] = (char) b;
				b = readByte();
			}
			return n == p ? buf : Arrays.copyOf(buf, p);
		}

		int nextInt() {
			int num = 0, b;
			boolean minus = false;
			while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'))
				;
			if (b == '-') {
				minus = true;
				b = readByte();
			}

			while (true) {
				if (b >= '0' && b <= '9') {
					num = num * 10 + (b - '0');
				} else {
					return minus ? -num : num;
				}
				b = readByte();
			}
		}

		long nextLong() {
			long num = 0;
			int b;
			boolean minus = false;
			while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'))
				;
			if (b == '-') {
				minus = true;
				b = readByte();
			}

			while (true) {
				if (b >= '0' && b <= '9') {
					num = num * 10 + (b - '0');
				} else {
					return minus ? -num : num;
				}
				b = readByte();
			}
		}

		char[][] nextMatrix(int n, int m) {
			char[][] map = new char[n][];
			for (int i = 0; i < n; i++)
				map[i] = next(m);
			return map;
		}

		int[] nextIntArray(int n) {
			int[] a = new int[n];
			for (int i = 0; i < n; i++)
				a[i] = nextInt();
			return a;
		}

		long[] nextLongArray(int n) {
			long[] a = new long[n];
			for (int i = 0; i < n; i++)
				a[i] = nextLong();
			return a;
		}

		int[][] next2DInt(int n, int m) {
			int[][] arr = new int[n][];
			for (int i = 0; i < n; i++) {
				arr[i] = nextIntArray(m);
			}
			return arr;
		}

		long[][] next2DLong(int n, int m) {
			long[][] arr = new long[n][];
			for (int i = 0; i < n; i++) {
				arr[i] = nextLongArray(m);
			}
			return arr;
		}

		int[] shuffle(int[] arr) {
			Random r = new Random();
			for (int i = 1, j; i < arr.length; i++) {
				j = r.nextInt(i);
				int c = arr[i];
				arr[i] = arr[j];
				arr[j] = c;
			}
			return arr;
		}

		long[] shuffle(long[] arr) {
			Random r = new Random();
			for (int i = 1, j; i < arr.length; i++) {
				j = r.nextInt(i);
				long c = arr[i];
				arr[i] = arr[j];
				arr[j] = c;
			}
			return arr;
		}

		int[] uniq(int[] arr) {
			arr = scn.shuffle(arr);
			Arrays.sort(arr);
			int[] rv = new int[arr.length];
			int pos = 0;
			rv[pos++] = arr[0];
			for (int i = 1; i < arr.length; i++) {
				if (arr[i] != arr[i - 1]) {
					rv[pos++] = arr[i];
				}
			}
			return Arrays.copyOf(rv, pos);
		}

		long[] uniq(long[] arr) {
			arr = scn.shuffle(arr);
			Arrays.sort(arr);
			long[] rv = new long[arr.length];
			int pos = 0;
			rv[pos++] = arr[0];
			for (int i = 1; i < arr.length; i++) {
				if (arr[i] != arr[i - 1]) {
					rv[pos++] = arr[i];
				}
			}
			return Arrays.copyOf(rv, pos);
		}

		int[] reverse(int[] arr) {
			int l = 0, r = arr.length - 1;
			while (l < r) {
				arr[l] = arr[l] ^ arr[r];
				arr[r] = arr[l] ^ arr[r];
				arr[l] = arr[l] ^ arr[r];
				l++;
				r--;
			}
			return arr;
		}

		long[] reverse(long[] arr) {
			int l = 0, r = arr.length - 1;
			while (l < r) {
				arr[l] = arr[l] ^ arr[r];
				arr[r] = arr[l] ^ arr[r];
				arr[l] = arr[l] ^ arr[r];
				l++;
				r--;
			}
			return arr;
		}

		int[] compress(int[] arr) {
			int n = arr.length;
			int[] rv = Arrays.copyOf(arr, n);
			rv = uniq(rv);
			for (int i = 0; i < n; i++) {
				arr[i] = Arrays.binarySearch(rv, arr[i]);
			}
			return arr;
		}

		long[] compress(long[] arr) {
			int n = arr.length;
			long[] rv = Arrays.copyOf(arr, n);
			rv = uniq(rv);
			for (int i = 0; i < n; i++) {
				arr[i] = Arrays.binarySearch(rv, arr[i]);
			}
			return arr;
		}

		void deepFillInt(Object array, int val) {
			if (!array.getClass().isArray()) {
				throw new IllegalArgumentException();
			}
			if (array instanceof int[]) {
				int[] intArray = (int[]) array;
				Arrays.fill(intArray, val);
			} else {
				Object[] objArray = (Object[]) array;
				for (Object obj : objArray) {
					deepFillInt(obj, val);
				}
			}
		}

		void deepFillLong(Object array, long val) {
			if (!array.getClass().isArray()) {
				throw new IllegalArgumentException();
			}
			if (array instanceof long[]) {
				long[] intArray = (long[]) array;
				Arrays.fill(intArray, val);
			} else {
				Object[] objArray = (Object[]) array;
				for (Object obj : objArray) {
					deepFillLong(obj, val);
				}
			}
		}
	}
}


import java.io.*;
import java.util.*;
import static java.lang.System.out;

public class Main {
    static MyReader in = new MyReader();

    public static void main(String[] args){
        String S = in.s();
        int len = S.length();
        int[] a = new int[2019];
        a[0] = 1;
        int x = 0;
         d = int;
        for (int i = len - 1; i >= 0; i--) {
            int j = S.charAt(i) - '0';
            x = (j * d + x) % 2019;
            a[x]++;
            d = 10 * d % 2019;
        }
        long ans = 0;
        for (int i = 0; i < 2019; i++) {
            ans += (long)a[i] * (a[i] - 1) / 2;
        }
        out.println(ans);
    }
}

class MyReader extends BufferedReader {
    char[] cbuf = new char[1024];
    int head = 0;
    int tail = 0;

    MyReader() {
        super(new InputStreamReader(System.in));
    }

    char next() {
        if (head == tail) {
            try {
                tail = read(cbuf, 0, cbuf.length);
            } catch (IOException e) {}
            head = 0;
        }
        return cbuf[head++];
    }

    void back() {
        head--;
    }

    boolean minus() {
        boolean minus;
        while (true) {
            char c = next();
            if (c != ' ' && c != '\n' && c != '\r') {
                if (!(minus = c == '-')) back();
                return minus;
            }
        }
    }

    void skip() {
        while (true) {
            char c = next();
            if (c != ' ' && c != '\n' && c != '\r') {
                back();
                break;
            }
        }
    }

    char[] s(final int N) {
        skip();
        char[] s = new char[N];
        for (int i = 0; i < s.length; i++) {
            s[i] = next();
        }
        return s;
    }

    String s() {
        try {
            return readLine();
        } catch (IOException e) {
            return "";
        }
    }

    int i() {
        boolean minus = minus();
        int n = 0;
        while (true) {
            int k = next() - '0';
            if (k < 0 || 9 < k) break;
            n = 10 * n + k;
        }
        return minus ? -n : n;
    }

    int[] ii(final int N) {
        int[] a = new int[N];
        for (int j = 0; j < a.length; j++) a[j] = i();
        return a;
    }

    long l() {
        boolean minus = minus();
        long n = 0;
        while (true) {
            int k = next() - '0';
            if (k < 0 || 9 < k) break;
            n = 10 * n + k;
        }
        return minus ? -n : n;
    }
}


import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        FastScanner sc = new FastScanner();
        String s = sc.next();
        int[] modSum = new int[s.length()];
        int n = modSum.length;
        modSum[n-1]=s.charAt(n-1)-'0';
        HashSet<Integer>[] data = new HashSet[2019];
        for (int i=0;i<2019;i++)data[i]=new HashSet<>();
        data[modSum[n-1]].add(n-1);
        int[] MOD2019 = new int[n];
        MOD2019[n-1]=1;
        for (int i=n-2;i>=0;i--){
            MOD2019[i]=MOD2019[i+1]*10%2019;
        }
        for (int i=n-2;i>=0;i--){
            modSum[i]=(modSum[i+1]+(s.charAt(i)-'0')*MOD2019[i])%2019;
            data[modSum[i]].add(i);
        }
        long ans = 0;
        for (int i=0;i<n;i++){
            for (int aa : data[modSum[i]]){
                if (aa>i){
                    ans++;
                }
            }
            if (modSum[i]==0)ans++;
        }
        System.out.println(ans);

    }

    public static class FastScanner {
        private final InputStream in = System.in;
        private final byte[] buffer = new byte[1024];
        private int ptr = 0;
        private int buflen = 0;
        private boolean hasNextByte() {
            if (ptr < buflen) {
                return true;
            }else{
                ptr = 0;
                try {
                    buflen = in.read(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (buflen <= 0) {
                    return false;
                }
            }
            return true;
        }
        private int readByte() { if (hasNextByte()) return buffer[ptr++]; else return -1;}
        private static boolean isPrintableChar(int c) { return 33 <= c && c <= 126;}
        public boolean hasNext() { while(hasNextByte() && !isPrintableChar(buffer[ptr])) ptr++; return hasNextByte();}
        public String next() {
            if (!hasNext()) throw new NoSuchElementException();
            StringBuilder sb = new StringBuilder();
            int b = readByte();
            while(isPrintableChar(b)) {
                sb.appendCodePoint(b);
                b = readByte();
            }
            return sb.toString();
        }
        public long nextLong() {
            if (!hasNext()) throw new NoSuchElementException();
            long n = 0;
            boolean minus = false;
            int b = readByte();
            if (b == '-') {
                minus = true;
                b = readByte();
            }
            if (b < '0' || '9' < b) {
                throw new NumberFormatException();
            }
            while(true){
                if ('0' <= b && b <= '9') {
                    n *= 10;
                    n += b - '0';
                }else if(b == -1 || !isPrintableChar(b)){
                    return minus ? -n : n;
                }else{
                    throw new NumberFormatException();
                }
                b = readByte();
            }
        }
        public int nextInt() {
            long nl = nextLong();
            if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE) throw new NumberFormatException();
            return (int) nl;
        }
        public double nextDouble() { return Double.parseDouble(next());}
    }
    public static class BasicMath {
        private final int MAX = 510000;
        private final int MOD = 1000000007;
        private final long[] fac = new long[MAX];
        private final long[] finv= new long[MAX];
        private final long[] inv = new long[MAX];
        public long factorial(long num){
            if (num<2)return 1;
            else return num*factorial(num-1);
        }
        public int arraySum(int[] array){
            int ans = 0;
            for (int value : array) ans += value;
            return ans;
        }
        public double log(double base,double antilogarithm){
            return Math.log(antilogarithm)/Math.log(base);
        }
        public int gcd(int x,int y){
            if (y==0)return x;
            else return gcd(y,x%y);
        }
        public int lcm(int x,int y){
            return x/gcd(x,y)*y;
        }
        public HashMap<Long,Long> factorization(long num){
            HashMap<Long,Long> hash = new HashMap<>();
            long n = num;
            long count = 2;
            while (n>1){
                while (n%count==0){
                    n/=count;
                    if (hash.containsKey(count))hash.put(count,hash.get(count)+1);
                    else hash.put(count,1L);
                }
                count++;
            }
            return hash;
        }
        public int sum(int[] num){
            int ans = 0;
            for (int i = 0; i < num.length;i++){
                ans += num[i];
            }
            return ans;
        }
        public int[] reduce(int small, int big){
            int ins = 2;
            while (ins<=small){
                if (small%ins==0&&big%ins==0){
                    small/=ins;
                    big/=ins;
                    ins = 2;
                }else {
                    ins++;
                }
            }
            return new int[]{small, big};
        }
        public int reduceCount(int small, int big){
            int ins = 2;
            int ans = 0;
            while (ins<=small){
                if (small%ins==0&&big%ins==0){
                    small/=ins;
                    big/=ins;
                    ins = 2;
                    ans++;
                }else {
                    ins++;
                }
            }
            return ans;
        }
        public int binarySearch(int[] array, int target){
            int pos = -1;
            int left = 0;
            int right = array.length-1;
            int middle;
            while (pos==-1&&left<=right){
                middle = (left+right)/2;
                if (array[middle]==target)pos=middle;
                else if (array[middle]>target)right=middle-1;
                else left=middle+1;
            }
            return pos;
        }
        public long modPow(long a, long n, long mod){
            long res = 1;
            while (n>0){
                if ((n&1)!=0)res = res*a%mod;
                a=a*a%mod;
                n >>= 1;
            }
            return res;
        }
        public long modinv(long a, long mod){return modPow(a,mod-2,mod);}
        public void COMinit(){
            fac[0]=1;
            fac[1]=1;
            finv[0]=1;
            finv[1]=1;
            inv[1]=1;
            for (int i=2;i<MAX;i++){
                fac[i]=fac[i-1]*i%MOD;
                inv[i]=MOD-inv[MOD%i]*(MOD/i)%MOD;
                finv[i]=finv[i-1]*inv[i]%MOD;
            }
        }
        public long COM(int n, int k){
            if (n<k)return 0;
            if (n<0||k<0)return 0;
            return fac[n]*(finv[k]*finv[n-k]%MOD)%MOD;
        }
        public long HCOM(int n, int k){
            return COM(n+k-1,k);
        }
    }
    public static class Queue<T>{
        private final int max = 1000000;
        private final T[] queue = (T[]) new Object[max];
        private int tail = 0; private int head = 0;
        public void init(){this.head = 0; tail = 0;}
        public boolean isEmpty(){return (head==tail);}
        public boolean isFull(){return (head == (tail+1)%max);}
        public void enqueue(T v){
            if (isFull()){
                System.out.println("error: queue is full.");
                return;
            }
            queue[tail++] = v;
            if (tail == max) tail = 0;
        }
        public T dequeue(){
            if (isEmpty()){
                System.out.println("error: queue is empty");
                return null;
            }
            T res = queue[head];
            ++head;
            if (head == max) head = 0;
            return res;
        }
    }
    public static class Stack<T>{
        private final int max = 100000;
        private final T[] stack =(T[]) new Object[max];
        private int top = 0;
        public boolean isEmpty(){return (top==0);}
        public boolean isFull(){return (top==max);}
        public void init(){top=0;}
        public void push(T v){
            if (isFull()){
                System.out.println("error: stack is full.");
                return;
            }
            stack[top++] = v;
        }
        public T pop(){
            if (isEmpty()){
                System.out.println("error: stack is empty");
                return null;
            }
            return stack[--top];
        }
    }
    public static class Pair<T>{
        private T A;
        private T B;
        Pair(T a,T b){
            A=a;
            B=b;
        }
        public void changeA(T a){A=a;}
        public void changeB(T b){B=b;}
        public void changeAB(T a,T b){A=a;B=b;}
        public static Comparator numberSortWithA = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Pair p = (Pair)o1;
                Pair q = (Pair)o2;
                if ((Long)p.A<(Long)q.A)return -1;
                else if ((long)p.A==(long)q.A)return 0;
                else return 1;
            }
        };
        public static Comparator numberSortWithB = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Pair p = (Pair)o1;
                Pair q = (Pair)o2;
                if ((long)p.B<(long)q.B)return -1;
                else if ((long)p.B==(long)q.B)return 0;
                else return 1;
            }
        };
    }
    public static class Graph<T>{
        private final HashMap<T,HashSet<T>> graph = (HashMap<T,HashSet<T>>) new Object();
        private T[] nodeList;
        private HashMap<T,Integer> dictionary = new HashMap<>();
        private int Size;
        private int[] Depth;
        Graph(T[] nodes){
            for (T t : nodes)graph.put(t,new HashSet<>());
            Size = nodes.length;
            nodeList = nodes.clone();
            for (int i=0;i<Size;i++)dictionary.put(nodeList[i],i);
        }
        public void addEdge(T node1,T node2){
            if (graph.containsKey(node1)&&graph.containsKey(node2)){
                graph.get(node1).add(node2);
                graph.get(node2).add(node1);
            }
        }
        public int[] Depth(T root){
            Queue<T> q = new Queue<>();
            q.enqueue(root);
            boolean[] seen = new boolean[Size];
            Arrays.fill(seen, false);
            seen[dictionary.get(root)]=true;
            int[] depth = new int[Size];
            depth[dictionary.get(root)]=0;
            while (!q.isEmpty()){
                T v = q.dequeue();
                int vNum = dictionary.get(v);
                for (T u : graph.get(v)){
                    if (!seen[dictionary.get(u)]){
                        int uNum = dictionary.get(u);
                        seen[uNum]=true;
                        q.enqueue(u);
                        depth[uNum]=depth[vNum]+1;
                    }
                }
            }
            Depth = depth.clone();
            return depth;
        }
    }
    public static class UnionFindTree{
        int[] parent;
        int[] rank;
        int[] Size;
        public UnionFindTree(int size){
            this.parent = new int[size];
            this.rank = new int[size];
            this.Size = new int[size];
            for (int i=0;i<size;i++){
                makeSet(i);
            }
        }
        public void makeSet(int i){
            parent[i]=i;
            rank[i]=0;
            Size[i]=1;
        }
        public void union(int x, int y){
            int xRoot = find(x);
            int yRoot = find(y);
            if (rank[xRoot]>rank[yRoot]){
                parent[yRoot] = xRoot;
                Size[xRoot]+=Size[yRoot];
            }else if (rank[xRoot]<rank[yRoot]){
                parent[xRoot] = yRoot;
                Size[yRoot]+=Size[xRoot];
            }else if (xRoot!=yRoot){
                parent[yRoot] = xRoot;
                rank[xRoot]++;
                Size[xRoot]+=Size[yRoot];
            }
        }
        public int find(int i){
            if (i!= parent[i]){
                parent[i]=find(parent[i]);
            }
            return parent[i];
        }
        public boolean same(int x, int y){
            return find(x) == find(y);
        }
        public int getSize(int i){return Size[find(i)];}
    }

}


import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        FastScanner sc = new FastScanner();
        String s = sc.next();
        int[] modSum = new int[s.length()];
        int n = modSum.length;;
        modSum[n-1]=s.charAt(n-1)-'0';
        HashSet<Integer>[] data = new HashSet[2019];
        for (int i=0;i<2019;i++)data[i]=new HashSet<>();
        data[modSum[n-1]].add(s.charAt(n-1)-'0');
        int[] MOD2019 = new int[n];
        MOD2019[n-1]=1;
        for (int i=n-2;i>=0;i--){
            MOD2019[i]=MOD2019[i+1]*10%2019;
        }
        for (int i=n-2;i>=0;i--){
            modSum[i]=(modSum[i+1]+(s.charAt(i)-'0')*MOD2019[i])%2019;
            data[modSum[i]].add(i);
        }
        long ans = 0;
        for (int i=0;i<n;i++){
            for (int aa : data[modSum[i]]){
                if (aa>i){
                    ans++;
                }
            }
            if (modSum[i]==0)ans++;
        }
        System.out.println(ans);

    }

    public static class FastScanner {
        private final InputStream in = System.in;
        private final byte[] buffer = new byte[1024];
        private int ptr = 0;
        private int buflen = 0;
        private boolean hasNextByte() {
            if (ptr < buflen) {
                return true;
            }else{
                ptr = 0;
                try {
                    buflen = in.read(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (buflen <= 0) {
                    return false;
                }
            }
            return true;
        }
        private int readByte() { if (hasNextByte()) return buffer[ptr++]; else return -1;}
        private static boolean isPrintableChar(int c) { return 33 <= c && c <= 126;}
        public boolean hasNext() { while(hasNextByte() && !isPrintableChar(buffer[ptr])) ptr++; return hasNextByte();}
        public String next() {
            if (!hasNext()) throw new NoSuchElementException();
            StringBuilder sb = new StringBuilder();
            int b = readByte();
            while(isPrintableChar(b)) {
                sb.appendCodePoint(b);
                b = readByte();
            }
            return sb.toString();
        }
        public long nextLong() {
            if (!hasNext()) throw new NoSuchElementException();
            long n = 0;
            boolean minus = false;
            int b = readByte();
            if (b == '-') {
                minus = true;
                b = readByte();
            }
            if (b < '0' || '9' < b) {
                throw new NumberFormatException();
            }
            while(true){
                if ('0' <= b && b <= '9') {
                    n *= 10;
                    n += b - '0';
                }else if(b == -1 || !isPrintableChar(b)){
                    return minus ? -n : n;
                }else{
                    throw new NumberFormatException();
                }
                b = readByte();
            }
        }
        public int nextInt() {
            long nl = nextLong();
            if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE) throw new NumberFormatException();
            return (int) nl;
        }
        public double nextDouble() { return Double.parseDouble(next());}
    }
    public static class BasicMath {
        private final int MAX = 510000;
        private final int MOD = 1000000007;
        private final long[] fac = new long[MAX];
        private final long[] finv= new long[MAX];
        private final long[] inv = new long[MAX];
        public long factorial(long num){
            if (num<2)return 1;
            else return num*factorial(num-1);
        }
        public int arraySum(int[] array){
            int ans = 0;
            for (int value : array) ans += value;
            return ans;
        }
        public double log(double base,double antilogarithm){
            return Math.log(antilogarithm)/Math.log(base);
        }
        public int gcd(int x,int y){
            if (y==0)return x;
            else return gcd(y,x%y);
        }
        public int lcm(int x,int y){
            return x/gcd(x,y)*y;
        }
        public HashMap<Long,Long> factorization(long num){
            HashMap<Long,Long> hash = new HashMap<>();
            long n = num;
            long count = 2;
            while (n>1){
                while (n%count==0){
                    n/=count;
                    if (hash.containsKey(count))hash.put(count,hash.get(count)+1);
                    else hash.put(count,1L);
                }
                count++;
            }
            return hash;
        }
        public int sum(int[] num){
            int ans = 0;
            for (int i = 0; i < num.length;i++){
                ans += num[i];
            }
            return ans;
        }
        public int[] reduce(int small, int big){
            int ins = 2;
            while (ins<=small){
                if (small%ins==0&&big%ins==0){
                    small/=ins;
                    big/=ins;
                    ins = 2;
                }else {
                    ins++;
                }
            }
            return new int[]{small, big};
        }
        public int reduceCount(int small, int big){
            int ins = 2;
            int ans = 0;
            while (ins<=small){
                if (small%ins==0&&big%ins==0){
                    small/=ins;
                    big/=ins;
                    ins = 2;
                    ans++;
                }else {
                    ins++;
                }
            }
            return ans;
        }
        public int binarySearch(int[] array, int target){
            int pos = -1;
            int left = 0;
            int right = array.length-1;
            int middle;
            while (pos==-1&&left<=right){
                middle = (left+right)/2;
                if (array[middle]==target)pos=middle;
                else if (array[middle]>target)right=middle-1;
                else left=middle+1;
            }
            return pos;
        }
        public long modPow(long a, long n, long mod){
            long res = 1;
            while (n>0){
                if ((n&1)!=0)res = res*a%mod;
                a=a*a%mod;
                n >>= 1;
            }
            return res;
        }
        public long modinv(long a, long mod){return modPow(a,mod-2,mod);}
        public void COMinit(){
            fac[0]=1;
            fac[1]=1;
            finv[0]=1;
            finv[1]=1;
            inv[1]=1;
            for (int i=2;i<MAX;i++){
                fac[i]=fac[i-1]*i%MOD;
                inv[i]=MOD-inv[MOD%i]*(MOD/i)%MOD;
                finv[i]=finv[i-1]*inv[i]%MOD;
            }
        }
        public long COM(int n, int k){
            if (n<k)return 0;
            if (n<0||k<0)return 0;
            return fac[n]*(finv[k]*finv[n-k]%MOD)%MOD;
        }
        public long HCOM(int n, int k){
            return COM(n+k-1,k);
        }
    }
    public static class Queue<T>{
        private final int max = 1000000;
        private final T[] queue = (T[]) new Object[max];
        private int tail = 0; private int head = 0;
        public void init(){this.head = 0; tail = 0;}
        public boolean isEmpty(){return (head==tail);}
        public boolean isFull(){return (head == (tail+1)%max);}
        public void enqueue(T v){
            if (isFull()){
                System.out.println("error: queue is full.");
                return;
            }
            queue[tail++] = v;
            if (tail == max) tail = 0;
        }
        public T dequeue(){
            if (isEmpty()){
                System.out.println("error: queue is empty");
                return null;
            }
            T res = queue[head];
            ++head;
            if (head == max) head = 0;
            return res;
        }
    }
    public static class Stack<T>{
        private final int max = 100000;
        private final T[] stack =(T[]) new Object[max];
        private int top = 0;
        public boolean isEmpty(){return (top==0);}
        public boolean isFull(){return (top==max);}
        public void init(){top=0;}
        public void push(T v){
            if (isFull()){
                System.out.println("error: stack is full.");
                return;
            }
            stack[top++] = v;
        }
        public T pop(){
            if (isEmpty()){
                System.out.println("error: stack is empty");
                return null;
            }
            return stack[--top];
        }
    }
    public static class Pair<T>{
        private T A;
        private T B;
        Pair(T a,T b){
            A=a;
            B=b;
        }
        public void changeA(T a){A=a;}
        public void changeB(T b){B=b;}
        public void changeAB(T a,T b){A=a;B=b;}
        public static Comparator numberSortWithA = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Pair p = (Pair)o1;
                Pair q = (Pair)o2;
                if ((Long)p.A<(Long)q.A)return -1;
                else if ((long)p.A==(long)q.A)return 0;
                else return 1;
            }
        };
        public static Comparator numberSortWithB = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Pair p = (Pair)o1;
                Pair q = (Pair)o2;
                if ((long)p.B<(long)q.B)return -1;
                else if ((long)p.B==(long)q.B)return 0;
                else return 1;
            }
        };
    }
    public static class Graph<T>{
        private final HashMap<T,HashSet<T>> graph = (HashMap<T,HashSet<T>>) new Object();
        private T[] nodeList;
        private HashMap<T,Integer> dictionary = new HashMap<>();
        private int Size;
        private int[] Depth;
        Graph(T[] nodes){
            for (T t : nodes)graph.put(t,new HashSet<>());
            Size = nodes.length;
            nodeList = nodes.clone();
            for (int i=0;i<Size;i++)dictionary.put(nodeList[i],i);
        }
        public void addEdge(T node1,T node2){
            if (graph.containsKey(node1)&&graph.containsKey(node2)){
                graph.get(node1).add(node2);
                graph.get(node2).add(node1);
            }
        }
        public int[] Depth(T root){
            Queue<T> q = new Queue<>();
            q.enqueue(root);
            boolean[] seen = new boolean[Size];
            Arrays.fill(seen, false);
            seen[dictionary.get(root)]=true;
            int[] depth = new int[Size];
            depth[dictionary.get(root)]=0;
            while (!q.isEmpty()){
                T v = q.dequeue();
                int vNum = dictionary.get(v);
                for (T u : graph.get(v)){
                    if (!seen[dictionary.get(u)]){
                        int uNum = dictionary.get(u);
                        seen[uNum]=true;
                        q.enqueue(u);
                        depth[uNum]=depth[vNum]+1;
                    }
                }
            }
            Depth = depth.clone();
            return depth;
        }
    }
    public static class UnionFindTree{
        int[] parent;
        int[] rank;
        int[] Size;
        public UnionFindTree(int size){
            this.parent = new int[size];
            this.rank = new int[size];
            this.Size = new int[size];
            for (int i=0;i<size;i++){
                makeSet(i);
            }
        }
        public void makeSet(int i){
            parent[i]=i;
            rank[i]=0;
            Size[i]=1;
        }
        public void union(int x, int y){
            int xRoot = find(x);
            int yRoot = find(y);
            if (rank[xRoot]>rank[yRoot]){
                parent[yRoot] = xRoot;
                Size[xRoot]+=Size[yRoot];
            }else if (rank[xRoot]<rank[yRoot]){
                parent[xRoot] = yRoot;
                Size[yRoot]+=Size[xRoot];
            }else if (xRoot!=yRoot){
                parent[yRoot] = xRoot;
                rank[xRoot]++;
                Size[xRoot]+=Size[yRoot];
            }
        }
        public int find(int i){
            if (i!= parent[i]){
                parent[i]=find(parent[i]);
            }
            return parent[i];
        }
        public boolean same(int x, int y){
            return find(x) == find(y);
        }
        public int getSize(int i){return Size[find(i)];}
    }

}


#pragma GCC optimize ("Ofast")
#pragma GCC target ("avx2")
//#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
#define rep(i, n) for(int i = 0; i < (n); i++)
#define rep1(i, n) for(int i = 1; i <= (n); i++)
#define co(x) cout << (x) << "\n"
#define cosp(x) cout << (x) << " "
#define ce(x) cerr << (x) << "\n"
#define cesp(x) cerr << (x) << " "
#define pb push_back
#define mp make_pair
#define chmin(x, y) x = min(x, y)
#define chmax(x, y) x = max(x, y)
#define Would
#define you
#define please

char S[200005];

int main() {
	//cin.tie(0);
	//ios::sync_with_stdio(false);

	fread(S, 1, 200005, stdin);
	int L = 0, R = 200005;
	while (L + 1 < R) {
		int M = (L + R) / 2;
		if (S[M] >= '0') L = M;
		else R = M;
	}

	int N = R;
	int a = 0;
	int A[2020] = {};
	A[0] = 1;

	ll kotae = 0;
	int tmp = 0;
	int are = 1;
	int shou = N / 5;
	int i = N - 1;
	int amari = N % 5;
	rep(j, shou) {
		tmp += (S[i--] - '0') * are;
		tmp %= 2019;
		kotae += A[tmp]++;
		are *= 10;

		tmp += (S[i--] - '0') * are;
		tmp %= 2019;
		kotae += A[tmp]++;
		are *= 10;

		tmp += (S[i--] - '0') * are;
		tmp %= 2019;
		kotae += A[tmp]++;
		are *= 10;

		tmp += (S[i--] - '0') * are;
		tmp %= 2019;
		kotae += A[tmp]++;
		are *= 10;

		tmp += (S[i--] - '0') * are;
		tmp %= 2019;
		kotae += A[tmp]++;
		are *= 10;
		are %= 2019;
	}
	rep(j, amari) {
		tmp += (S[i--] - '0') * are;
		tmp %= 2019;
		kotae += A[tmp]++;
		are *= 10;
	}

	printf("%lld\n", kotae);

	Would you please return 0;
}


#include<cstdio>
#include<cstring>

typedef long long ll;
const int maxn = 2E+5 + 5;

int n;
ll S, ans, base;
int sum[2025];
char s[maxn];

int main() {
	scanf("%s", s + 1);
	n = std::strlen(s + 1);

	sum[0] = 1, base = 1;
	for(int i = n; i >= 1; --i) {
		S = (S + (s[i] - '0') * base) % 2019ll;
		ans += sum[S]++, base = base * 10 % 2019ll;
	}
	printf("%lld", ans);
}


#include <stdio.h>
#include <string.h>
#define maxn 200010
#define mod 2019
#define LL long long
char s[maxn];
int cnt[mod];
LL ans;
int main()
{
    int n,i,x,y;
    scanf("%s",s+1);
    n=strlen(s+1),y=1,x=0;
    for(i=n; i>=1; i--)
    {
        x=(x+(s[i]-'0')*y)%mod;
        y=y*10%mod;
        cnt[x]++;
    }
    for(i=1; i<2019; i++)
        if(cnt[i]>1)
            ans+=(LL)(cnt[i]-1+1)*(cnt[i]-1)/2;
    ans+=(LL)cnt[0]*(cnt[0]+1)/2;//特殊余数处理
    printf("%lld\n",ans);
    return 0;
}


#include<algorithm>
#include<bitset>
#include<cmath>
#include<complex>
#include<deque>
#include<functional>
#include<iomanip>
#include<iostream>
#include<iterator>
#include<map>
#include<numeric>
#include<queue>
#include<set>
#include<stack>
#include<string>
#include<unordered_map>
#include<unordered_set>
#include<utility>
#include<vector>
#include<chrono>
using namespace std;
#define rep(i,n) for(int i=0; i<(n); i++)
#define FOR(i,x,n) for(int i=x; i<(n); i++)
#define vint(a,n) vint a(n); rep(i, n) cin >> a[i];
#define vll(a,n) vll a(n); rep(i, n) cin >> a[i];
#define ALL(n) begin(n),end(n)
#define RALL(n) rbegin(n),rend(n)
#define MOD (1000000007)
// #define MOD (998244353)
#define INF (2e9)
#define INFL (2e18)

typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
using vint=vector<int>;
using vll=vector<ll>;
using vbool=vector<bool>;
template<class T>using arr=vector<vector<T>>;
template<class T>int popcount(T &a){int c=0; rep(i, 8*(int)sizeof(a)){if((a>>i)&1) c++;} return c;}
template<class T>void pr(T x){cout << x << endl;}
template<class T>void prvec(vector<T>& a){rep(i, a.size()-1){cout << a[i] << " ";} pr(a[a.size()-1]);}
template<class T>void prarr(arr<T>& a){rep(i, a.size()) if(a[i].empty()) pr(""); else prvec(a[i]);}
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return 1; } return 0; }

int main()
{
    string s; cin >> s;
    int n = s.size();
    vint a(2019);
    vint md(2019);
    md[0] = 1;
    FOR(i, 1, 2019){
        md[i] = md[i-1]*10;
        md[i] %= 2019;
    }
    a[0]++;
    // prvec(md);
    reverse(ALL(s));

    ll t = 0;
    rep(i, n){
        // pr(t);
        t += (s[i]-'0') * md[i];
        t %= 2019;
        a[t]++;
    }

    ll ans = 0;
    rep(i, 2019){
        ans += a[i]*(a[i]-1)/2;
    }
    pr(ans);
    return 0;}


#include <bits/stdc++.h>
using namespace std;

int main ()
{
    string  s ;
    cin >> s;
    reverse(s.begin(),s.end()) ;
    map <int,int> c;
    int  ss=0,ans=0,cur=1;
    c[0] = 1 ;
    for (int i=0;i<s.size();i++){
        ss = (ss+((s[i]-'0')*cur))%2019;
        ans += c[ss] ;
        c[ss]++ ;
        cur = (cur*10)%2019;
    }
    cout << ans%2019 << endl ;
    return 0;
}


#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef long double ld;
typedef vector<vector<int>> Graph;
const int dx[4] = {1, 0, -1, 0};
const int dy[4] = {0, 1, 0, -1};
#define pi 3.14159265359
#define inf 2147483647
#define INF 9223372036854775807
#define mod 1000000007
#define mod2 998244353

int modPow(long long a, long long n, long long p) {
    if (n == 0) return 1;
    if (n == 1) return a % p;
    if (n % 2 == 1) return (a * modPow(a, n - 1, p)) % p;
    long long t = modPow(a, n / 2, p);
    return (t * t) % p;
}

int main() {
	string S; cin >> S;
	int size = S.size();
	vector<ll> right(size);
	map<int, int> m;
	for(int i = size - 1; i >= 0; i--) {
		if(i == size - 1) {
			right[size - 1] = S[size - 1] - '0';
			m[right[size - 1]] = 1;
			continue;
		}
		right[i] = (right[i + 1] + (S[i] - '0') * modPow(10, size - 1 - i, 2019)) % 2019;
		if(!m.count(right[i])) m[right[i]] = 1;
		else m[right[i]]++;
	}
	int ans = 0;
	for(auto p: m) {
		ans += (p.second * (p.second - 1)) / 2;
		if(p.first == 0) ans++;
	}
	cout << ans << endl;
	return 0;
}


#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <math.h>
#ifdef __cplusplus
#include <bits/stdc++.h>
#endif

uint32_t nextpint(void){ char c=getchar_unlocked(); while(c<'0'||'9'<c) c=getchar_unlocked(); uint32_t x=0; while('0'<=c && c<='9'){ x=x*10+c-'0'; c=getchar_unlocked(); } return x; }
uint32_t nextstr(char *s){ char c=getchar_unlocked(); while(c==' '||c=='\n') c=getchar_unlocked(); uint32_t len=0; while(c!=' '&&c!='\n'){ *s++=c-'0'; len++; c=getchar_unlocked(); } *s='\0'; return len; }
void printint(uint32_t x){ if(x<0){x=-x; putchar_unlocked('-');}else if(x==0){putchar_unlocked('0');} int d=0; char s[10]; while(x){ s[d]='0'+x%10; x/=10; d++; } for (int i=d-1; i>=0; i--) putchar_unlocked(s[i]); putchar_unlocked('\n'); }
void rsort_str(void *base, const int count, const int size);

char s[200001];
uint32_t m[2019]={1};

int main(void){
	uint32_t n=nextstr(s);
	uint32_t a=0, b=1;
	for(int32_t i=n-1; i>=0; i--){
		a+=b*s[i];
		a%=2019;
		m[a]++;
		b*=10;
		b%=2019;
	}
	uint64_t ans=0;
	for(uint32_t i=0; i<2019; i++){
		ans+=(uint64_t)m[i]*(m[i]-1)/2;
	}
	printf("%"PRIu64"\n", ans);
}


#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <math.h>
#ifdef __cplusplus
#include <bits/stdc++.h>
#endif

uint32_t nextpint(void){ char c=getchar_unlocked(); while(c<'0'||'9'<c) c=getchar_unlocked(); uint32_t x=0; while('0'<=c && c<='9'){ x=x*10+c-'0'; c=getchar_unlocked(); } return x; }
uint32_t nextstr(char *s){ char c=getchar_unlocked(); while(c==' '||c=='\n') c=getchar_unlocked(); uint32_t len=0; while(c!=' '&&c!='\n'){ *s++=c-'0'; len++; c=getchar_unlocked(); } *s='\0'; return len; }
void printint(uint32_t x){ if(x<0){x=-x; putchar_unlocked('-');}else if(x==0){putchar_unlocked('0');} int d=0; char s[10]; while(x){ s[d]='0'+x%10; x/=10; d++; } for (int i=d-1; i>=0; i--) putchar_unlocked(s[i]); putchar_unlocked('\n'); }
void rsort_str(void *base, const int count, const int size);

char s[200001];
uint32_t m[2019]={1};

int main(void){
	uint32_t n=read(0, s, sizeof(s))-1;
	uint32_t a=0, b=1;
	for(int32_t i=n-1; i>=0; i--){
		a+=b*(s[i]-'0');
		a%=2019;
		m[a]++;
		b*=10;
		b%=2019;
	}
	uint64_t ans=0;
	for(uint32_t i=0; i<2019; i++){
		ans+=(uint64_t)m[i]*(m[i]-1)/2;
	}
	printf("%"PRIu64"\n", ans);
}


#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<stdbool.h>
#include<assert.h>
typedef long long ll;
typedef long double ld;
#define rep(i,l,r)for(ll i=(l);i<(r);i++)
#define repp(i,l,r,k)for(ll i=(l);i<(r);i+=(k))
#define rrep(i,l,r)for(ll i=(l);i>=(r);i--)
#define INF (1LL<<60)
#define MOD1 1000000007
#define MOD2 998244353
#define MAX_N (1 << 17)
#define YES printf("Yes\n")
#define NO printf("No\n")
#define PN printf("\n")
#define charsize 100005 //10^5+5
#define PI 3.141592653589793238

void swap(ll *a, ll *b){ll c;c=*b;*b=*a;*a= c;}
void cin(ll *n){ scanf("%lld",&(*n)); }
ll max2(ll a,ll b){return a>=b?a:b;}
ll min2(ll a,ll b){return a>=b?b:a;}
ll min3(ll a, ll b, ll c){return (a<=b && a<=c) ? a : b<=c ? b : c;}
ll max3(ll a, ll b, ll c){return (a>=b && a>=c) ? a : b>=c ? b : c;}
ll minn(ll n, ll a[]){ll b=INF;rep(i,0,n) b=min2(b,a[i]);return b;}
ll maxn(ll n, ll a[]){ll b=-INF;rep(i,0,n) b=max2(b,a[i]);return b;}
ll POW(ll a, ll b){ll c=1;rep(i,0,b) c*=a;return c;}
double POW_d(double a, double b){double c=1;rep(i,0,b) c*=a;return c;}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;}
ll mod_MOD1(ll n){n+= n<0?((-n)/MOD1+1)*MOD1:0; return n%=MOD1;}
ll mod_p(ll n ,ll p){n+= n<0?((-n)/p+1)*p:0; return n%=p;}
ll change_into_num(char s[] , ll len, ll p){ return !p ? 0 : POW(10,p-1)*(s[len-p]-'0') + change_into_num(s,len,p-1); }

void lr_lower( int *l, int *r, ll am, ll val , int type ){ (type<3) ? ( am < val ?  ( *l = (*l+*r)/2 ) : ( *r= (*l+*r)/2 ) ) : ( am <= val ? ( *l = (*l+*r)/2 ) : ( *r= (*l+*r)/2 ) ); }
void lr_upper( int *l, int *r, ll am, ll val , int type ){ (type<3) ? ( am <= val ?  ( *l = (*l+*r)/2 ) : ( *r= (*l+*r)/2 ) ) : ( am < val ? ( *l = (*l+*r)/2 ) : ( *r= (*l+*r)/2 ) ); }
int cmp_lower( ll a, ll b, int type ){ return (type==1) ? ( a==b ? 1 : 0 ) : (type==2) ? ( a>=b ? 1 : 0 ) : ( a>b ? 1 : 0 ) ; }
int cmp_upper( ll a, ll b, int type ){ return (type==1) ? ( a==b ? 1 : 0 ) : (type==2) ? ( a<=b ? 1 : 0 ) : ( a<b ? 1 : 0 ) ; }
// return smallest p  which meets  a[p]==val :1  >=:2   >:3
ll lower_bound( ll a[], int l, int r, ll val , int type ){  while(r-l>1) lr_lower(&l,&r,a[ (l+r)/2 ],val,type);  return cmp_lower(a[l],val,type) ? l : cmp_lower(a[r],val,type) ? r : -1;  }
// return biggest p  which meets   a[p]==val :1  <=:2   <:3
ll upper_bound( ll a[], int l, int r, ll val , int type ){  while(r-l>1) lr_upper(&l,&r,a[ (l+r)/2 ],val,type); return cmp_upper(a[r],val,type) ? r : cmp_upper(a[l],val,type) ? l : -1; }
// count i  which meets ai==x
ll count(ll a[], int l, int r, ll x){  int p = lower_bound(a,l,r,x,1);  return p==-1 ? 0 : upper_bound(a,p,r,x,1)-p+1; }
ll *factors[2] , fac_cnt=0;
ll factor_pre(ll x){  rep(i,0,2){ factors[i] = realloc( factors[i] , sizeof(ll) ); factors[i][i?fac_cnt++:fac_cnt]=i?0:2; } repp(i,3,x+1,2) rep(j,0,fac_cnt) if( !(i%factors[0][j]) ){break;} else if(POW(factors[0][j],2)>i){  rep(k,0,2){ factors[k] = realloc( factors[k] , sizeof(ll)*( fac_cnt +1 ) );  factors[k][k?fac_cnt++:fac_cnt]=k?0:i;  } break; }  return 0; }
ll factor(ll n, ll is_update){ rep(i,0,fac_cnt){ ll cnt=0; rep(j,0,1){ while( ( cnt+= n %factors[0][i]==0 ? 1 : 0 )  && (n/=factors[0][i]) %factors[0][i]==0 ) continue;} factors[1][i]= is_update ? cnt : max2(factors[1][i],cnt);  if( factors[1][i]*factors[1][i] > n ) break; }    return n; }
ll judge_prime(ll n){ rep(i,0,fac_cnt){ if(n<factors[0][i]*factors[0][i] || n==factors[0][i]) break; else if(n%factors[0][i]==0) n/=n; }  return n==1?0:1; }

int upll(const void*a, const void*b){return*(ll*)a<*(ll*)b?-1:*(ll*)a>*(ll*)b?1:0;}
int downll(const void*a, const void*b){return*(ll*)a<*(ll*)b?1:*(ll*)a>*(ll*)b?-1:0;}
int cmp_string( const void * a , const void * b ) {  return strcmp( (char *)a , (char *)b ); }  // qsort((void*)s,n,sizeof(s[0]),int_sort );
int cmp_char(const void * a, const void * b) { return *(char *)a - *(char *)b;}
void sortup(ll*a,int n){qsort(a,n,sizeof(ll),upll);}
void sortdown(ll*a,int n){qsort(a,n,sizeof(ll),downll);}
void sort_string(int n,int size,char s[][size]){ qsort( (void*)s , n , sizeof(s[0]) , cmp_string ); }
void sort_char(char *s){ qsort( (void *)s , strlen(s) , sizeof(char) , cmp_char ); }
ll unique_string(ll n ,ll size, char s[][size]){ ll ans=1; rep(i,1,n) if( strcmp(s[i],s[i-1]) ) ans++; return ans; }
ll unique_num(ll n , ll a[]){ ll ans=1; rep(i,1,n) if( a[i]!=a[i-1] ) ans++; return ans; }

typedef struct{ ll a , b;}fr;
int cmp1( const void *p, const void *q ) { return ((fr*)p) ->a - ((fr*)q)->a;}
int cmp2( const void *p, const void *q ) { return ((fr*)q) ->a - ((fr*)p)->a;}
void strsortup(fr*a,int n){qsort(a,n,sizeof(fr),cmp1);}
void strsortdown(fr*a,int n){qsort(a,n,sizeof(fr),cmp2);}

int main(void){
    // fgets(s,sizeof(s),stdin);
    char s[200005];
    scanf("%s",s);
    ll n=strlen(s);
    ll ans=0;
    cin(&n);
    ll a[2050]={};
    a[0]=1;
    ll dig=1,tmp=0;
    rrep(i,n-1,0){
        tmp+=dig*(s[i]-'0');
        tmp%=2019;
        a[tmp]++;
        dig=dig*10%2019;
    }
    rep(i,0,2050){
        if(a[i]<2) continue;
        ans+=a[i]*(a[i]-1)/2;
    }

    printf("%lld\n",ans);
    return 0;
}


printf("%d",kind);


#include <vector.h>
using namespace std;

#define rep(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define xrep(i, a, b) for (int i = int(a); i < int(b); ++i)
using ll = long long;
const int INF = 1001001001;

vector<int> num;
int cnt = 0;
bool visited[300000][300000];
int N = 0;

void dfs(int i, int j) {
    if (visited[j][i]==true) return;
    if(i==N+1) {
        return;
    }
    if(j==N+1) {
        return;
    }
    visited[j][i] = true;
    int n = 0;
    for(int k=0; k<j-i; k++) {
        n += num[i+k]*pow(10, k);
    }
    if(n%2019==0 && n!=0) {
        cnt++;
    }
    dfs(i, j+1);
    dfs(i+1, j);
}

int main() {
    string S;
    cin >> S;
    for(auto s: S) {
        num.push_back(s-'0');
    }
    reverse(num.begin(), num.end());
    N = num.size();
    dfs(0, 0);
    print(cnt);

    return 0;
}


#include <vector>
using namespace std;

#define rep(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define xrep(i, a, b) for (int i = int(a); i < int(b); ++i)
using ll = long long;
const int INF = 1001001001;

vector<int> num;
int cnt = 0;
bool visited[300000][300000];
int N = 0;

void dfs(int i, int j) {
    if (visited[j][i]==true) return;
    if(i==N+1) {
        return;
    }
    if(j==N+1) {
        return;
    }
    visited[j][i] = true;
    int n = 0;
    for(int k=0; k<j-i; k++) {
        n += num[i+k]*pow(10, k);
    }
    if(n%2019==0 && n!=0) {
        cnt++;
    }
    dfs(i, j+1);
    dfs(i+1, j);
}

int main() {
    string S;
    cin >> S;
    for(auto s: S) {
        num.push_back(s-'0');
    }
    reverse(num.begin(), num.end());
    N = num.size();
    dfs(0, 0);
    print(cnt);

    return 0;
}


"use strict";
var input=require("fs").readFileSync("/dev/stdin","utf8");
var cin=input.split(/ |\n/),cid=0;
function next(a){return a?cin[cid++]:+cin[cid++];}
function nexts(n,a){return a?cin.slice(cid,cid+=n):cin.slice(cid,cid+=n).map(a=>+a);}
function nextm(h,w,a){var r=[],i=0;if(a)for(;i<h;i++)r.push(cin.slice(cid,cid+=w));else for(;i<h;i++)r.push(cin.slice(cid,cid+=w).map(a=>+a));return r;}
function xArray(v){var a=arguments,l=a.length,r="Array(a["+--l+"]).fill().map(x=>{return "+v+";})";while(--l)r="Array(a["+l+"]).fill().map(x=>"+r+")";return eval(r);}

var myOut = main();
if(myOut !== undefined)console.log(myOut);

function main(){
  var s = next(1);
  var map = xArray(0,2020);
  var x = 0, ans = 0, k = 1;
  map[0] = 1;
  for(var i = s.length; i--;){
    x += +s[i] * k;
    x %= 2019;
    k = k*10%2019;
    ans += map[x]++;
  }
  return ans;
}


const main = input => {
  const mod = 2019
  let s = input.trim().split('')
  let ln = s.length

  let ms = new Array(ln+1).fill(0)
  let cr = 1
  for(let i = ln-1; 0 <= i; i--) {
    ms[i] = (ms[i+1] + cr*Number(s[i])) % mod
    cr = (cr*10) % mod
  }

  let cnt = new Array(mod).fill(0)
  ms.forEach(v => cnt[v] += 1)
  let ans = 0
  cnt.forEach(v => {if(v >= 2) ans += v*(v-1)/2})
  console.log(ans.toString())
}

main(require("fs").readFileSync("/dev/stdin", "utf8"))


"use strict";

const main = arg => {
    arg = arg.trim().split("\n");
    const S = (arg[0].split(" ")[0]);
    const N = S.length;

    let answer = 0;
    let cnt = [...Array(2020)].fill(0);
    cnt[0] = 1;

    let total = 0;
    let p = 1;

    for(let i=N-1; i>=0; i--) {
        total = (total + parseInt(S[i]) * p) % 2019;

        // console.log("total:" + total);
        // console.log("S[i]:" + S[i]);

        answer += cnt[total];

        p = (p * 10) % 2019;
        // console.log("p:" + p);
        // console.log("------")
        cnt[total]++;
    }

    // console.log(cnt);
    console.log(answer);
}
main(require('fs').readFileSync('/dev/stdin', 'utf8'));


console.log((args=>{
  const S = args.trim().split``.map(v=>v|0);
  const L = S.length;
  const M = Array(2019).fill(0);
  let e = 0, d = 1;
  for ( let i = L-1; i >=0; i-- ) {
    e = (e + S[i] * d)%2019;
    M[e]++;
    d = d*10%2019;
  }
  let a = M[0];
  for ( let i = 1; i < 2019; i++ ) {
    if ( M[i] < 2 ) continue;
    a += M[i] * (M[i]-1) / 2;
  }
  return `${a}`;
})(require('fs').readFileSync('/dev/stdin', 'utf8')));


let input = require("fs").readFileSync("/dev/stdin", "utf8")

function main() {
    const [S] = load(1,1,String)

    let result = 0

    for(let i = 0; i < S.length - 3; i++) {
        for(let j = i + 3; j < S.length; j++) {
            const n = Number(S.slice(i, j + 1))
            if(n % 2019 === 0) result++
        }
    }

    return result
}

/*

入力値を読み込む
load(rows = 1, cols = 1, type = Number)

TruthyならYes, FalsyならNo
yesno(boolean) => String

数列の和
array_sum(numbers) => Number

対無限ループ安全装置
inifinite_loop_preventer(max = 1e5)

配列の中から最大値を返す
array_max(numbers, initial_value = null) => Number

配列の中から最小値を返す
array_min(numbers, initial_value = null) => Number

オブジェクトのスナップショットをconsole.logする
log_obj(value)

nを素因数分解
math_prime_factors(n) => { 素数: n乗 }

2次元配列を作る
new_array_2d(x_len, y_len = 0, init_value = 0) => [[]]

最大公約数
gcd(a, b) => Number

最小公倍数
lcm(a,b) => Number

subsetはsetの部分集合である
check_if_superset(set, subset) => Boolean

和集合
set_union(a, b) => Set

積集合
set_intersection(a, b) => Set

対称差集合
set_symmetric_difference(a, b) => Set

差集合
set_difference(a, b) => Set

配列からc個取る組み合わせ全列挙
set_combinations(array, count) => [[]]

文字列版splice（not in place）
string_splice(str, index, delete_count, add_str = '')

*/

let inputs = input.split(/[\s\n]/)
let inifinite_loop_prevent_counter = 0
let result = main()
console.log(result)

if(result === undefined) {
    console.warn("result is undefined")
}
// console.timeEnd()

// 入力値を読み込む
function load(rows = 1, cols = 1, type = Number) {
    const length = rows * cols
    let result = inputs.splice(0, length)

    if(type !== String) result = result.map(type)
    if(cols > 1) {
        const arrays = new Array(cols)
        for(let col = 0; col < cols; col++) {
            arrays[col] = new Array()
            for(let row = 0; row < rows; row++) {
                arrays[col].push(result[row * cols + col])
            }
        }
        result = arrays
    }

    return result
}

// TruthyならYes, FalsyならNo
function yesno(value) {
    return value ? 'Yes' : 'No'
}

// 数列の和
function array_sum(numbers) {
    return numbers.reduce((acc, cur) => acc + cur, 0)
}

// 対無限ループ安全装置
function inifinite_loop_preventer(max = 1e5) {
    inifinite_loop_prevent_counter++
    if(inifinite_loop_prevent_counter >= max) {
        throw new Error("Infinite loop")
    }
}

// 配列の中から最大値を返す
function array_max(numbers, initial_value = null) {
    let result = (initial_value === null) ? numbers[0] : initial_value
    for(const n of numbers) if(n > result) result = n
    return result
}

// 配列の中から最小値を返す
function array_min(numbers, initial_value = null) {
    let result = (initial_value === null) ? numbers[0] : initial_value
    for(const n of numbers) if(n < result) result = n
    return result
}

// オブジェクトのスナップショットをconsole.logする
function log_obj(value) {
    console.log(JSON.parse(JSON.stringify(value)))
}

// 素因数分解 { 素数: n乗 }
function math_prime_factors(n) {
    const result = {}

    for(let i = 2; i * i <= n; i++) {
        if(n % i === 0) {
            n /= i
            result[i] = 1

            while(n % i === 0) {
                n /= i
                result[i]++
            }
        }
    }

    if(n > 1) result[n] = 1

    return result
}

// 2次元配列を作る
function new_array_2d(x_len, y_len = 0, init_value = 0) {

    const array = new Array()
    for(let i = 0; i < x_len; i++) {
        const y = y_len ? new Array(y_len).fill(init_value) : new Array()
        array.push(y)
    }

    return array
}

// 最大公約数
function gcd(a, b) {
    if(b === 0) return a
    return gcd(b, a % b)
}

// 最小公倍数
function lcm(a,b) {
    const g = (n, m) => m ? g(m, n % m) : n
    return a * b / g(a, b)
}

// subsetはsetの部分集合である
function check_if_superset(set, subset) {
    for(let el of subset) {
        if(!set.has(el)) {
            return false
        }
    }
    return true
}

// 和集合
function set_union(a, b) {
    const result = new Set(a)
    for(const el of b) result.add(el)
    return result
}

// 積集合
function set_intersection(a, b) {
    let result = new Set()
    for(let el of b) {
        if(a.has(el)) {
            result.add(el)
        }
    }
    return result
}

// 対称差集合
function set_symmetric_difference(a, b) {
    let result = new Set(a)
    for(let el of b) {
        if(result.has(el)) {
            result.delete(el)
        } else {
            result.add(el)
        }
    }
    return result
}

// 差集合
function set_difference(a, b) {
    let result = new Set(a)
    for(let el of b) result.delete(el)
    return result
}

// 配列からc個取る組み合わせ全列挙
function set_combinations(array, count) {

    const result = []

    function _combi({ pre = [], rest }) {

        if(pre.length + rest.length < count) return

        if(pre.length < count) {
            for(let i = 0; i < rest.length; i++) {
                _combi({
                    pre: [...pre, rest[i]],
                    rest: rest.slice(0,i)
                })
            }
        } else {
            result.push(pre)
        }
    }

    _combi({ rest: array })

    return result
}

function math_permutation(n, r) {
    let res = 1
    for(let i = 0; i < r; i++) {
        res *= n - i
    }
    return res
}

function math_combination(n, r) {
    return math_permutation(n, r) / math_permutation(r, r)
}

// 文字列版splice（not in place）
function string_splice(str, index, delete_count, add_str = '') {
    if(index < 0) {
        index = str.length + index
        if(index < 0) index = 0
    }
    return str.slice(0, index) + add_str + str.slice(index + delete_count)
}


const S = require("fs").readFileSync("/dev/stdin", "utf8")
const mod = 2019
const map = Array(2020).fill(0)
map[0] = 1
let c = 0
let p = 1
let m = 0

for (let i = S.length - 1; i >= 0; i--) {
    m = (m + S[i] * p) % mod
    c += map[m]
    map[m]++
    p = p * 10 % mod
}

console.log(c)


package main

import (
	"bufio"
	"fmt"
	"os"
)

const (
	initialBufSize = 10000
	maxBufSize     = 100000000
)

var sc *bufio.Scanner = func() *bufio.Scanner {
	sc := bufio.NewScanner(os.Stdin)
	buf := make([]byte, initialBufSize)
	sc.Buffer(buf, maxBufSize)
	return sc
}()

// Input string
func scanString() (s string) {
	sc.Scan()
	return sc.Text()
}

func main() {
	input := scanString()

	remSumArray := make([]int, 2019)
	remSumArray[0] = 1
	var num int
	digit := 1

	for i := len(input) - 1; i >= 0; i-- {
		num += int(input[i]-48) * digit
		digit *= 10
		digit %= 2019
		//fmt.Println(num, "\t", num%2019)
		remSumArray[num%2019]++
	}

	//fmt.Println(remSumArray)
	var result int
	for _, num := range remSumArray {
		result += num * (num - 1) / 2
	}

	fmt.Println(result)
}


package main

import (
	"bufio"
	"fmt"
	"math"
	"os"
	"strconv"
)

const (
	initialBufSize = 10000
	maxBufSize     = 100000000
)

var sc *bufio.Scanner = func() *bufio.Scanner {
	sc := bufio.NewScanner(os.Stdin)
	buf := make([]byte, initialBufSize)
	sc.Buffer(buf, maxBufSize)
	return sc
}()

// var sc = bufio.NewScanner(os.Stdin)
// var buff []byte
//
// func init() {
// 	sc.Split(bufio.ScanWords)
// 	sc.Buffer(buff, 100000000)
// 	log.SetFlags(log.Lshortfile)
// }

func main() {
	input := nextString()
	remSumArray := make([]int, 2019)
	remSumArray[0] = 1
	var num int
	digit := 1
	for i := len(input) - 1; i >= 0; i-- {
		num += int(input[i]-48) * digit
		digit *= 10
		digit %= 2019
		remSumArray[num%2019]++
	}
	var result int
	for _, num := range remSumArray {
		result += num * (num - 1) / 2
	}
	fmt.Println(result)
}

func nextString() (s string) {
	sc.Scan()
	return sc.Text()
}

func nextFloat64() float64 {
	sc.Scan()
	f, err := strconv.ParseFloat(sc.Text(), 64)
	if err != nil {
		panic(err)
	}
	return f
}

func nextInt() (i int) {
	sc.Scan()
	i, err := strconv.Atoi(sc.Text())
	if err != nil {
		panic(err)
	}
	return i
}

func nextInts(n int) (r []int) {
	r = make([]int, n)
	for i := 0; i < n; i++ {
		r[i] = nextInt()
	}
	return r
}

var dy = []int{0, 1, 0, -1}
var dx = []int{1, 0, -1, 0}
var MAX = math.MaxInt32

func pow(a, b int) int {
	return int(math.Pow(float64(a), float64(b)))
}

func lcm(a, b int) int {
	return a * b / gcd(a, b)
}
func gcd(a, b int) int {
	if a < b {
		a, b = b, a
	}
	for b != 0 {
		a, b = b, a%b
	}
	return a
}
func max(a ...int) int {
	r := a[0]
	for i := 0; i < len(a); i++ {
		if r < a[i] {
			r = a[i]
		}
	}
	return r
}


package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
)

// const (
// 	initialBufSize = 10000
// 	maxBufSize     = 100000000
// )
//
// var sc *bufio.Scanner = func() *bufio.Scanner {
// 	sc := bufio.NewScanner(os.Stdin)
// 	buf := make([]byte, initialBufSize)
// 	sc.Buffer(buf, maxBufSize)
// 	return sc
// }()

var sc = bufio.NewScanner(os.Stdin)
var buff []byte

func init() {
	sc.Buffer(buff, 100000000)
	log.SetFlags(log.Lshortfile)
}

func main() {
	input := nextString()
	remSumArray := make([]int, 2019)
	remSumArray[0] = 1
	var num int
	digit := 1
	for i := len(input) - 1; i >= 0; i-- {
		num += int(input[i]-48) * digit
		digit *= 10
		digit %= 2019
		remSumArray[num%2019]++
	}
	var result int
	for _, num := range remSumArray {
		result += num * (num - 1) / 2
	}
	fmt.Println(result)
}

func nextString() (s string) {
	sc.Scan()
	return sc.Text()
}


package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

var sc = bufio.NewScanner(os.Stdin)

func nextInt() int {
	sc.Scan()
	i, e := strconv.Atoi(sc.Text())
	if e != nil {
		panic(e)
	}
	return i
}

func nextString() string {
	sc.Scan()
	return sc.Text()
}

func Reverse(s string) string {
	runes := []rune(s)
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return string(runes)
}

func dfs(s string) int {
	ans := 0
	sl := len(s)

	mod := 2019
	s = Reverse(s)

	sum := 0
	cnt := make([]int, sl)

	cnt[0] = 0
	x := 1

	for i := 0; i < sl; i++ {
		cnt[sum]++
		a, _ := strconv.Atoi(string(s[i]))
		sum += (a * x)
		sum = sum % mod
		ans += cnt[sum]
		x = x * 10 % mod
	}
	return ans
}

func main() {
	sc.Split(bufio.ScanWords)
	sc.Buffer(nil, 1000000000000000)

	ss := nextString()

	fmt.Println(dfs(ss))
}


package main

import (
	"bufio"
	"fmt"
	"math"
	"os"
	"strconv"
)

var sc = bufio.NewScanner(os.Stdin)

func nextInt() int {
	sc.Scan()
	i, e := strconv.Atoi(sc.Text())
	if e != nil {
		panic(e)
	}
	return i
}

func nextString() string {
	sc.Scan()
	return sc.Text()
}

func Reverse(s string) string {
	runes := []rune(s)
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return string(runes)
}

func dfs(s string) int {
	ans := 0
	sl := len(s)

	mod := 2019
	s = Reverse(s)

	sum := 0
	cnt := make([]int, sl+1)
	hist := make(map[int]bool)

	cnt[0] = 0
	hist[0] = true

	for i := 1; i <= sl; i++ {
		a, _ := strconv.Atoi(string(s[i-1]))
		sum += (a * int(math.Pow10(i-1))) % mod
		cnt[i] = sum % mod
		if hist[cnt[i]] {
			ans++
		}
		hist[cnt[i]] = true
	}
	return ans
}

func main() {
	sc.Split(bufio.ScanWords)
	sc.Buffer(nil, 100000000)

	ss := nextString()

	fmt.Println(dfs(ss))
}


package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

var sc = bufio.NewScanner(os.Stdin)

func nextInt() int {
	sc.Scan()
	i, e := strconv.Atoi(sc.Text())
	if e != nil {
		panic(e)
	}
	return i
}

func nextString() string {
	sc.Scan()
	return sc.Text()
}

func dfs(s string) int {
	ans := 0
	for i := 0; i < len(s); i++ {
		for j := i + 3; j <= len(s); j++ {
			// fmt.Println(s[i:j])
			num, _ := strconv.Atoi(s[i:j])
			if num%2019 == 0 {
				ans++
			}
		}
	}
	return ans
}

func main() {
	sc.Split(bufio.ScanWords)
	ss := nextString()

	fmt.Println(dfs(ss))
}


g=[a=0]*M=2019
gets.bytes{g[a%M]+=1;a+=_1%48*$.=$.*202%M}
p g.sum{_1*~-_1/2}


g=[a=0]*M=2019;g[0]=t=1;gets.chop.reverse.bytes{g[a=(a+_1%48*t=t*10%M)%M]+=1};p g.sum{_1*~-_1/2}


g=[x=a=-1]*M=2019
t = 1
gets.bytes do
  x-=g[a%M]+=1
  t = t*202%M
  a+=_1%48*t
end
p~x


S = gets.chomp

ans = 0
c = 0
mods = [0] * 2019
mods[0] = 1
S.chars.reverse.each_with_index do |s,i|
    c = (c + s.to_i * (10**i)) % 2019
    ans += mods[c]
    mods[c] += 1
end

p ans


S = gets.chomp

ans = 0
(0..S.size-4).each do |i|
    (i+1..S.size-1).each do |j|
        ans += 1 if S[i..j].to_i % 2019 == 0
    end
end

p ans


s = gets.to_i
s_length = s.to_s.length

ans = 0

temp = 0
for i in 0..s_length-4 do
	for j in 4..s_length-i do
		temp = s.to_s.slice(i,j)
		if temp.to_i % 2019 == 0 then
			ans += 1
		end
	end
end

puts ans


use std::io::stdin;
// use std::collections::HashMap;

fn main() {
    let mut s: String = String::new();
    stdin().read_line(&mut s).ok();
    let s: Vec<char> = s.trim().chars().collect();

    let mut counts: Vec<usize> = vec![0; 2019];
    counts[0] += 1;

    // s $B$rA`:n$7$F$$$-!"(Bmod 2019 $B$r(B count $B$H$7$FJ]B8$7$F$$$/(B
    let mut base = 1;
    let mut temp_mod = 0;
    for &s_i in s.iter().rev() {
        temp_mod = (temp_mod + (s_i as usize - 48) * base) % 2019;
        base = (base * 10) % 2019;
        if s_i != '0' {
            counts[temp_mod] += 1;
        }
    }

    let mut res = 0;
    for counts_i in counts {
        if counts_i > 1 {
            res += counts_i * (counts_i - 1) / 2;
        }
    }
    println!("{}", res);
}

// fn main() {
//     let mut s: String = String::new();
//     stdin().read_line(&mut s).ok();
//     let s: Vec<char> = s.trim().chars().collect();
//
//     // $B8e$m$+$iAv::$7$F$$$/(B
//     // $B3FCmL\?t$K$D$$$F!">jM>$r=P$7$F!"0l$D:8$N>jM>$rB-$7$F(B... $B$r7+$jJV$7$F$$$/(B
//
//     let mut res = 0;
//     let mut dp_dict: HashMap<usize, usize> = HashMap::new();
//     for i_upper in 0..s.len() {
//         let mut temp_mod = 0;
//         let mut temp_dp_dict: HashMap<usize, usize> = HashMap::new();
//         for i_lower in i_upper..s.len() {
//             // println!("{}, {}", s[i_lower], s[i_lower] as usize);
//             temp_mod = (temp_mod * 10 + (s[i_lower] as usize - 48)) % 2019;
//
//             // temp_mod = (temp_mod + (s[i_upper] as usize) * base);
//
//             // let temp_str: String = s[i_upper..i_lower + 1].iter().collect();
//             // // println!("{}, {}", temp_str, temp_str.parse::<usize>().unwrap() % 2019);
//             // // temp_mod = (temp_mod + temp_str.parse::<usize>().unwrap() % 2019) % 2019;
//             // temp_mod = temp_str.parse::<usize>().unwrap() % 2019;
//             // // println!(" === {} === ", temp_mod);
//
//             if temp_mod == 0 {
//                 if dp_dict.contains_key(&i_lower) {
//                     res += dp_dict.get(&i_lower).unwrap();
//                     for (k, v) in temp_dp_dict.iter_mut() {
//                         *v += 1;
//                     }
//                     break;
//                 }
//                 res += 1;
//                 temp_dp_dict.insert(i_lower, 0);
//                 for (k, v) in temp_dp_dict.iter_mut() {
//                     *v += 1;
//                 }
//             }
//         }
//         for (k, v) in temp_dp_dict.iter() {
//             dp_dict.insert(*k, *v);
//         }
//     }
//
//     println!("{}", res);
// }


use std::io::stdin;fn main(){let mut st = String::new();stdin().read_line(&mut st).unwrap();let s:Vec<char>=st.trim().chars().collect();let mut t=1;let mut c:usize=0;let mut r:[usize;2020]=[0;2020];let m=2019 as usize;for i in (0..s.len()).rev(){let x=(s[i] as usize)-48;c=(c+x*t)%m;r[c]+=1;t=(t*10)%m;};c=0;for i in 0..m{if &1<&r[i] {c+=(r[i]-1)*r[i]/2;}};c+=r[0];println!("{}",c)}


use std::io::stdin;
fn main() {
    let mut st = String::new();
    stdin().read_line(&mut st).unwrap();
    let s:Vec<char> = st.trim().chars().collect();
    let mut ten = 1;
    let mut c:usize = 0;
    let mut r:[i64;2020] = [0;2020];
    let m = s.len() as usize;
    for i in 1..m+1 {
        let x = (s[m-i] as usize)-48;
        c = (c+x*ten)%2019;
        r[c] += 1;
        ten = (ten*10)%2019;
    };
    let mut result:i64 = 0;
    for i in 0..2019 {
        if &1 < &r[i] {
            result += (r[i]-1)*r[i]/2
        };
    };
    result += r[0];
    println!("{}", result);
}


// -*- coding:utf-8-unix -*-

#[macro_use]
extern crate lazy_static;
extern crate num_bigint;
// 0.2.2
extern crate num_traits;

// 0.2.8
use num_bigint::BigInt;
use num_bigint::ToBigInt;
use num_traits::{Pow, ToPrimitive};
use num_traits::{One, Zero};

// use proconio::derive_readable;
use itertools::*;
use itertools_num::*;
use proconio::fastout;
use proconio::input;
// use std::convert::TryInto;
// use ascii::Chars;
// use libm::*;
use proconio::marker::{Bytes, Chars};
use std::cmp::*;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
use std::io::*;
use std::ops::Range;
use std::str::FromStr;
use superslice::*;

use lazy_static::lazy_static;
use std::sync::Mutex;

pub fn read<T: FromStr>() -> T {
    let stdin = stdin();
    let stdin = stdin.lock();
    let token: String = stdin
        .bytes()
        .map(|c| c.expect("failed to read char") as char)
        .skip_while(|c| c.is_whitespace())
        .take_while(|c| !c.is_whitespace())
        .collect();
    token.parse().ok().expect("failed to parse token")
}

lazy_static! {
    // static ref PARENTS: Mutex<Vec<usize>> = Mutex::default();
    // static ref CHILDS: Mutex<Vec<Vec<usize>>> = Mutex::default();
    static ref ADJ: Mutex<Vec<Vec<usize>>> = Mutex::default();
}

// const MOD: usize = 9997063;
// const MOD: usize = 100_000_000_ + 7;

//abc164-D
// #[fastout]
fn main() {
    input![s: Chars];

    let mut is_2019 = vec![Vec::new(); 2019];
    for i in 0..s.len() {
        let s: String = s.iter().as_slice()[i..].iter().collect();
        let s = BigInt::from_str(&s).unwrap();
        // eprintln!("{}, {}", s, s % 2019);
        let modulo = (s % 2019.to_bigint().unwrap()).to_usize().unwrap();
        let last_e = *is_2019[modulo].last().unwrap_or(&0);
        is_2019[modulo].push(1 + last_e);
    }

    let last_e = *is_2019[0].last().unwrap_or(&0);
    is_2019[0].push(1 + last_e);

    // let mut is_2019_comsum = is_2019.iter().fold(
    //     vec![], |mut mid, a| {
    //         mid.push(
    //             mid.last().unwrap_or(&0) + if *a { 1 } else { 0 }
    //         );
    //         mid
    //     },
    // );
    let is_2019: Vec<_> = is_2019.iter().map(|mod_count| *mod_count.last().unwrap_or(&0)).collect();
    // eprintln!("{:?}", is_2019.iter().filter_map(|a| if *a > 0 {Some(*a)} else{None}).collect::<Vec<_>>());

    let mut ans = 0;
    for c in is_2019.iter(){
        ans += if *c <= 1 {0}else{
            c * (c - 1) / 2
        }
    }

    // eprintln!("{:?}", is_2019);

    // let num2019 = is_2019_comsum.last().unwrap();
    // let mut ans = 0;
    // for i in 0..is_2019_comsum.len(){
    //     ans +=  num2019 - is_2019_comsum[i]
    // };

    // eprintln!("{:?}", adj);
    println!("{}", ans);
}

// let mut values = VALUES.lock().unwrap();
// values.extend_from_slice(&[1, 2, 3, 4]);
// assert_eq!(&*values, &[1, 2, 3, 4]);

// -100000000

//  1000000000


// -*- coding:utf-8-unix -*-

#[macro_use]
extern crate lazy_static;
extern crate num_bigint;
// 0.2.2
extern crate num_traits;

// 0.2.8
use num_bigint::BigInt;
use num_bigint::ToBigInt;
use num_traits::Pow;
use num_traits::{One, Zero};

// use proconio::derive_readable;
use itertools::*;
use itertools_num::*;
use proconio::fastout;
use proconio::input;
// use std::convert::TryInto;
// use ascii::Chars;
// use libm::*;
use proconio::marker::{Bytes, Chars};
use std::cmp::*;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
use std::io::*;
use std::ops::Range;
use std::str::FromStr;
use superslice::*;

use lazy_static::lazy_static;
use std::sync::Mutex;

pub fn read<T: FromStr>() -> T {
    let stdin = stdin();
    let stdin = stdin.lock();
    let token: String = stdin
        .bytes()
        .map(|c| c.expect("failed to read char") as char)
        .skip_while(|c| c.is_whitespace())
        .take_while(|c| !c.is_whitespace())
        .collect();
    token.parse().ok().expect("failed to parse token")
}

lazy_static! {
    // static ref PARENTS: Mutex<Vec<usize>> = Mutex::default();
    // static ref CHILDS: Mutex<Vec<Vec<usize>>> = Mutex::default();
    static ref ADJ: Mutex<Vec<Vec<usize>>> = Mutex::default();
}

// const MOD: usize = 9997063;
// const MOD: usize = 100_000_000_ + 7;

//abc164-D
// #[fastout]
fn main() {
    input![s: Chars];

    let mut is_2019 = vec![Vec::new(); 2019];
    for i in 0..s.len() {
        let s: String = s.iter().as_slice()[i..].iter().collect();
        let s = usize::from_str(&s).unwrap();
        // eprintln!("{}, {}", s, s % 2019);
        let last_e = *is_2019[s % 2019].last().unwrap_or(&0);
        is_2019[s % 2019].push(1 + last_e);
    }

    let last_e = *is_2019[0].last().unwrap_or(&0);
    is_2019[0].push(1 + last_e);

    // let mut is_2019_comsum = is_2019.iter().fold(
    //     vec![], |mut mid, a| {
    //         mid.push(
    //             mid.last().unwrap_or(&0) + if *a { 1 } else { 0 }
    //         );
    //         mid
    //     },
    // );
    let is_2019: Vec<_> = is_2019.iter().map(|mod_count| *mod_count.last().unwrap_or(&0)).collect();
    // eprintln!("{:?}", is_2019.iter().filter_map(|a| if *a > 0 {Some(*a)} else{None}).collect::<Vec<_>>());

    let mut ans = 0;
    for c in is_2019.iter(){
        ans += if *c <= 1 {0}else{
            c * (c - 1) / 2
        }
    }

    // eprintln!("{:?}", is_2019);

    // let num2019 = is_2019_comsum.last().unwrap();
    // let mut ans = 0;
    // for i in 0..is_2019_comsum.len(){
    //     ans +=  num2019 - is_2019_comsum[i]
    // };

    // eprintln!("{:?}", adj);
    println!("{}", ans);
}

// let mut values = VALUES.lock().unwrap();
// values.extend_from_slice(&[1, 2, 3, 4]);
// assert_eq!(&*values, &[1, 2, 3, 4]);

// -100000000

//  1000000000


// 答えを見た
#![allow(non_snake_case)]
use proconio::input;
// use std::collections::HashMap;

fn main() {
    input! {
        S: String,
    }

    // 2019 = 3 * 673
    // まずはNaive解を書いてみる
    // O(N^2) なので4*10^10 アウト
    // let mut ans = 0;
    // for i in 0..S.len() {
    //     for j in i + 1..S.len() {
    //         let s: &str = &S[i..j + 1];

    //         let num: i64 = s.parse().unwrap();
    //         if num % 2019 == 0 {
    //             ans += 1;
    //         }
    //     }
    // }

    // println!("{}", ans);

    // (i, j)が2019で割れるとはどういうことか
    // (i, N-1) % 2019 == (j+1, N-1) % 2019
    // ここで、「割り切れる」ではなくて「余りが同じ」という関係に持ち込んでいるのがポイント
    // 「割り切れる」だと当てはまるものが少ない。問題を分割したいのにサイズが小さくなっていない感じ

    let N = S.len();
    let mut Tmod2019s: Vec<i64> = Vec::with_capacity(N + 1);
    Tmod2019s.resize(N + 1, 0);

    let mut Rs: Vec<i64> = Vec::with_capacity(2019);
    Rs.resize(2019, 0);

    // 最初、「DPで~」ということが分かっていなかった。
    // |S|<=200000なので、Sは20万桁の数になりうる。
    // よって、Sそのものをparse()するとあふれてエラーになる。
    // そこで、剰余の性質を使って求めていく

    for i in (0..N).rev() {
        // これは収まらない
        // let s: &str = &S[i..N];
        // let num: i64 = s.parse().unwrap();

        // これもあふれる
        // Tmod2019s[i] = S[i..i+1] * 10^(N-i-1) + Tmod2019s[i+1];

        let mut num: i64 = (&S[i..i + 1]).parse().unwrap();
        for _ in 0..(N - i - 1) {
            num = (num * 10) % 2019;
        }

        num = (num + Tmod2019s[i + 1]) % 2019;
        Tmod2019s[i] = num;

        Rs[Tmod2019s[i] as usize] += 1;
    }

    // 2019の倍数であるような部分文字列は、引かなくてもそれ単体で2019の倍数
    // これを見逃していて、数時間困っていた。
    let mut ans = Rs[0];

    // ペアにして引いて2019の倍数を作る
    for i in 0..2019 {
        ans += Rs[i] * (Rs[i] - 1) / 2;
    }

    println!("{}", ans);
}


import Foundation

func readInt() -> [Int] {
    return readLine()!.split(separator: " ").map { c in Int(c)! }
}

func main() {
    let S = readLine()!

    var x = 0
    var pow10 = 1
    var counts = Array(repeating: 0, count:2019)
    counts[0] = 1
    for c in S.reversed() {
        x += Int(String(c))! * pow10
        pow10 *= 10
        x %= 2019
        pow10 %= 2019
        counts[x] += 1
    }
    let ans = counts.reduce(0) {$0 + $1 * ($1 - 1) / 2}

    print(ans)
}

main()