use std::io::*;
use std::str::FromStr;

#[allow(dead_code)]
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")
}

type Weight = i64;
#[allow(dead_code)]
#[derive(Clone, Debug, Eq, PartialEq)]
struct Edge {
    index: usize,
    src: usize,
    dest: usize,
    weight: Weight,
}
#[allow(dead_code)]
impl Edge {
    fn new(index: usize, src: usize, dest: usize, weight: Weight) -> Edge {
        Edge {
            index: index,
            src: src,
            dest: dest,
            weight: weight,
        }
    }
}
impl std::cmp::Ord for Edge {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        (self.weight, self.src, self.dest).cmp(&(other.weight, other.src, other.dest))
    }
}
impl std::cmp::PartialOrd for Edge {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}
type Edges = Vec<Edge>;
type Graph = Vec<Edges>;
#[allow(dead_code)]
fn graph_new(n: usize) -> Graph {
    vec![vec![]; n]
}
#[allow(dead_code)]
fn add_bi_edge(graph: &mut Graph, index: &mut usize, src: usize, dest: usize, weight: Weight) {
    graph[src].push(Edge::new(*index, src, dest, weight));
    graph[dest].push(Edge::new(*index, dest, src, weight));
    *index += 1;
}
#[allow(dead_code)]
fn add_uni_edge(graph: &mut Graph, index: &mut usize, src: usize, dest: usize, weight: Weight) {
    graph[src].push(Edge::new(*index, src, dest, weight));
    *index += 1;
}

fn main() {
    let l = read::<i64>();
    let g = solve(l);
    let n = g.len();
    let m = g.iter().fold(0, |v, es| v + es.len());
    println!("{} {}", n, m);
    for i in 0..n {
        for e in g[i].iter() {
            println!("{} {} {}", e.src + 1, e.dest + 1, e.weight);
        }
    }
}

fn solve(l: i64) -> Graph {
    let n = 20;
    let mut g = graph_new(n);
    let mut index = 0;
    let mut mask = 0;
    let mut prev = false;
    for i in 0..(n - 1) {
        let bit = 1 << (n - i - 2);
        if l >= (bit << 1) {
            add_uni_edge(&mut g, &mut index, i, i + 1, 0);
            add_uni_edge(&mut g, &mut index, i, i + 1, bit);
            if prev {
                add_uni_edge(&mut g, &mut index, 0, i, l & mask);
            }
            mask |= bit << 1;
        }
        if (l & bit) > 0 {
            prev = true;
        } else {
            prev = false;
        }
    }
    if prev {
        add_uni_edge(&mut g, &mut index, 0, n - 1, l - 1);
    }
    return g;
}

#[test]
fn test() {
    fn dfs(g: &Graph, from: usize, total: i64, visit: &mut Vec<i64>) -> bool {
        if from == g.len() - 1 {
            if visit[total as usize] != 0 {
                return false;
            }
            visit[total as usize] = 1;
        }
        for e in g[from].iter() {
            if !dfs(g, e.dest, total + e.weight, visit) {
                return false;
            }
        }
        return true;
    }
    let l = 1000000i64;
    // for l in 1..1000 {
    let g = solve(l);
    let mut visit = vec![0; l as usize];
    if !dfs(&g, 0, 0, &mut visit) {
        assert!(false, format!("l={}: visit twice", l));
    }
    for i in 0..l as usize {
        assert!(visit[i] == 1, format!("l={}: not visited {}", l, i));
    }
    for i in 0..g.len() {
        for e in g[i].iter() {
            assert!(e.weight <= 1e+6 as i64, format!("l={}: too heavy", l));
        }
    }
    let n = g.len();
    let m = g.iter().fold(0, |v, es| v + es.len());
    assert!(n <= 20, format!("l={}: too many vertex", l));
    assert!(m <= 60, format!("l={}: too many edge", l));
    // }
}


Measured memory_bytes: 4352