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 = i32;
#[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]
}

use std::collections::VecDeque;
// index ^ 1 is reverse edge
#[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));
    *index += 1;
    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;
    graph[dest].push(Edge::new(*index, dest, src, 0));
    *index += 1;
}

#[allow(dead_code)]
fn augment(
    g: &Graph,
    capacity: &mut Vec<Weight>,
    level: &Vec<usize>,
    finished: &mut Vec<bool>,
    from: usize,
    t: usize,
    cur: Weight,
) -> Weight {
    if from == t || cur == 0 {
        return cur;
    }
    if finished[from] {
        return 0;
    }
    for edge in g[from].iter() {
        let to = edge.dest;
        if level[to] != level[from] + 1 {
            continue;
        }
        let ncur = std::cmp::min(cur, capacity[edge.index]);
        let f = augment(g, capacity, level, finished, to, t, ncur);
        if f > 0 {
            capacity[edge.index] -= f;
            capacity[edge.index ^ 1] += f;
            return f;
        }
    }
    finished[from] = true;
    0
}

#[allow(dead_code)]
fn max_flow(g: &Graph, e: usize, s: usize, t: usize) -> Weight {
    let n = g.len();
    let mut capacity: Vec<Weight> = vec![0; e];
    for from in 0..n {
        for edge in g[from].iter() {
            capacity[edge.index] += edge.weight;
        }
    }
    let mut ans = 0;
    loop {
        let mut level = vec![usize::max_value(); n];
        level[s] = 0;
        let mut que = VecDeque::<usize>::new();
        que.push_back(s);
        while let Some(from) = que.pop_front() {
            if from == t {
                break;
            }
            for edge in g[from].iter() {
                let to = edge.dest;
                if capacity[edge.index] > 0 && level[to] == usize::max_value() {
                    que.push_back(to);
                    level[to] = level[from] + 1;
                }
            }
        }
        let mut finished = vec![false; n];
        let mut end = true;
        loop {
            let f = augment(
                g,
                &mut capacity,
                &level,
                &mut finished,
                s,
                t,
                Weight::max_value(),
            );
            if f == 0 {
                break;
            }
            ans += f;
            end = false;
        }
        if end {
            break;
        }
    }
    ans
}

struct Solver {
    n: usize,
    m: usize,
    lr: Vec<(usize, usize)>,
}

impl Solver {
    fn new() -> Solver {
        let n = read::<usize>();
        let m = read::<usize>();
        let mut lr = vec![(0, 0); n];
        for i in 0..n {
            lr[i].0 = read::<usize>();
            lr[i].1 = read::<usize>();
        }
        Solver { n: n, m: m, lr: lr }
    }
    fn IN(&self, x: usize) -> usize {
        x
    }
    fn LEFT(&self, x: usize) -> usize {
        x + self.n
    }
    fn RIGHT(&self, x: usize) -> usize {
        x + self.n + self.m
    }
    fn OUT(&self, x: usize) -> usize {
        x + self.n + self.m * 2
    }
    fn SOURCE(&self) -> usize {
        self.n + self.m * 3
    }
    fn DEST(&self) -> usize {
        self.n + self.m * 3 + 1
    }
    fn SIZE(&self) -> usize {
        self.n + self.m * 3 + 2
    }
    fn solve(&mut self) {
        let mut g = graph_new(self.SIZE());
        let mut e = 0;
        for i in 0..self.n {
            add_uni_edge(&mut g, &mut e, self.SOURCE(), self.IN(i), 1);
            if self.lr[i].0 != 0 {
                add_uni_edge(&mut g, &mut e, self.IN(i), self.LEFT(self.lr[i].0 - 1), 1);
            }
            if self.lr[i].1 != self.m + 1 {
                add_uni_edge(&mut g, &mut e, self.IN(i), self.RIGHT(self.lr[i].1 - 1), 1);
            }
        }
        for i in 0..self.m {
            if i != 0 {
                add_uni_edge(&mut g, &mut e, self.LEFT(i), self.LEFT(i - 1), 1);
            }
            if i != self.m - 1 {
                add_uni_edge(&mut g, &mut e, self.RIGHT(i), self.RIGHT(i + 1), 1);
            }
            add_uni_edge(&mut g, &mut e, self.LEFT(i), self.OUT(i), 1);
            add_uni_edge(&mut g, &mut e, self.RIGHT(i), self.OUT(i), 1);
            add_uni_edge(&mut g, &mut e, self.OUT(i), self.DEST(), 1);
        }
        let mut ans = self.n - max_flow(&g, e, self.SOURCE(), self.DEST()) as usize;
        println!("{}", ans);
    }
}

fn main() {
    let mut solver = Solver::new();
    solver.solve();
}
