#![allow(unused_imports, unused_macros, unknown_lints)]

use std::collections::*;
use std::cmp::Ordering::*;

macro_rules! dump {($($e:expr), +) => {print!(concat!($(stringify!($e), " = {:?}\n"), +), $($e), +)}}
macro_rules! max {($e0:expr, $($e:expr), +) => {{let mut r = $e0; $(r = std::cmp::max(r, $e);)+ r}}}
macro_rules! min {($e0:expr, $($e:expr), +) => {{let mut r = $e0; $(r = std::cmp::min(r, $e);)+ r}}}

fn main() {
  let (n,k) = get::tuple::<usize,usize>();
  let xycs = get::tuple3s::<usize,usize,String>(n);

  let xys =
    xycs.iter().
    map(|&(x,y,ref c)|if &c[..]=="W" {((x+k)%(2*k),y%(2*k))} else {(x%(2*k),y%(2*k))}).
    collect::<Vec<_>>();
  
  let mut mat = vec![vec![0; 2*k]; 2*k];
  for &(x,y) in &xys {mat[x][y] += 1}
  
  let mut grid = Grid::new(mat, 0);
  grid.accumulate();
  
  let offset = |p,d| {
    let q = p + d as isize;
    if q < 1 {0} else if q > 2 * k as isize {2*k+1} else {q as usize}
  };
  
  // if k==1 {dump!(grid.height, grid.width); grid.print()}

  let mut ans = 0;
  for i in 0 .. 2*k {
    for j in 0 .. 2*k {
      let k = k as isize;
      let sum_c  = grid.sum(offset(1,i),offset(1,j),offset(k,i),offset(k,j));
      let sum_w  = grid.sum(offset(1-2*k,i),offset(1,j),offset(k-2*k,i),offset(k,j));
      let sum_s  = grid.sum(offset(1,i),offset(1-2*k,j),offset(k,i),offset(k-2*k,j));
      let sum_nw = grid.sum(offset(1-k,i),offset(1+k,j),offset(k-k,i),offset(k+k,j));
      let sum_ne = grid.sum(offset(1+k,i),offset(1+k,j),offset(k+k,i),offset(k+k,j));
      let sum_sw = grid.sum(offset(1-k,i),offset(1-k,j),offset(k-k,i),offset(k-k,j));
      let sum_se = grid.sum(offset(1+k,i),offset(1-k,j),offset(k+k,i),offset(k-k,j));
      // if k==1 {dump!(sum_c,sum_w,sum_s,sum_nw,sum_ne,sum_sw,sum_se)}
      ans = max!(ans, sum_c + sum_w + sum_s + sum_nw + sum_ne + sum_sw + sum_se)
    }
  }
  
  println!("{}", ans)
}

struct Grid {
  height: usize,
  width: usize,
  mat: Vec<Vec<isize>>
}

#[allow(dead_code)]
impl Grid {
  fn new(mat: Vec<Vec<isize>>, b: isize) -> Grid {
    let h = mat.len();
    let w = mat[0].len();
    let mut m = mat;

    for v in m.iter_mut() {
      v.insert(0, b);
      v.push(b)
    }
    let bs = (0..w+2).map(|_|b).collect::<Vec<_>>();
    m.insert(0, bs.clone());
    m.push(bs);

    Grid {height: h, width: w, mat: m}
  }
 
  fn get(&self, i: usize, j: usize) -> isize {
    self.mat[i][j]
  }
 
  fn put(&mut self, i: usize, j: usize, x: isize) {
    self.mat[i][j] = x
  }
 
  fn accumulate(&mut self) {
    for i in 1 .. self.height + 1 {
      for j in 1 .. self.width + 1 {
        self.mat[i][j] += self.mat[i][j-1]
      }
    }
    for i in 1 .. self.height + 1 {
      for j in 1 .. self.width + 1 {
        self.mat[i][j] += self.mat[i-1][j]
      }
    }
  }
 
  fn sum(&self, x1: usize, y1: usize, x2: usize, y2: usize) -> isize {
    if x1>self.height || y1>self.width {return 0}
    let x1 = if x1 <= 0 {0} else {x1-1};
    let y1 = if y1 <= 0 {0} else {y1-1};
    let x2 = if x2 > self.height {self.height} else {x2};
    let y2 = if y2 > self.width {self.width} else {y2};
    self.mat[x2][y2] - self.mat[x1][y2] - self.mat[x2][y1] + self.mat[x1][y1]
  }
 
  fn print(&self) {
    for v in self.mat[1 .. self.height+1].iter() {
      let out = &v[1..self.width+1].iter().map(|e|format!("{:?}",e)).collect::<Vec<_>>().as_slice().join(" ");
      println!("{}", out);
    }
  }
}

#[allow(dead_code)]
mod get {
  use std::io::*;
  use std::str::*;
  use std::process::*;

  pub fn val<T: FromStr>() -> T {
    let mut buf = String::new();
    let s = stdin();
    s.lock().read_line(&mut buf).ok();
    if buf.is_empty() {println!("No input"); exit(0)}
    buf.trim_right().parse::<T>().ok().unwrap()
  }

  pub fn vals<T: FromStr>(n: usize) -> Vec<T> {
    let mut vec: Vec<T> = vec![];
    for _ in 0 .. n {
      vec.push(val());
    }
    vec
  }

  pub fn tuple<T1: FromStr, T2: FromStr>() -> (T1, T2) {
    let mut buf = String::new();
    let s = stdin();
    s.lock().read_line(&mut buf).ok();
    if buf.is_empty() {println!("No input"); exit(0)}
    let mut it = buf.trim_right().split_whitespace();
    let x = it.next().unwrap().parse::<T1>().ok().unwrap();
    let y = it.next().unwrap().parse::<T2>().ok().unwrap();
    (x, y)
  }

  pub fn tuples<T1: FromStr, T2: FromStr>(n: usize) -> Vec<(T1, T2)> {
    let mut vec: Vec<(T1, T2)> = vec![];
    for _ in 0 .. n {
      vec.push(tuple());
    }
    vec
  }

  pub fn tuple3<T1: FromStr, T2: FromStr, T3: FromStr>() -> (T1, T2, T3) {
    let mut buf = String::new();
    let s = stdin();
    s.lock().read_line(&mut buf).ok();
    if buf.is_empty() {println!("No input"); exit(0)}
    let mut it = buf.trim_right().split_whitespace();
    let x = it.next().unwrap().parse::<T1>().ok().unwrap();
    let y = it.next().unwrap().parse::<T2>().ok().unwrap();
    let z = it.next().unwrap().parse::<T3>().ok().unwrap();
    (x, y, z)
  }

  pub fn tuple3s<T1: FromStr, T2: FromStr, T3: FromStr>(n: usize) -> Vec<(T1, T2, T3)> {
    let mut vec: Vec<(T1, T2, T3)> = vec![];
    for _ in 0 .. n {
      vec.push(tuple3());
    }
    vec
  }

  pub fn vec<T: FromStr>() -> Vec<T> {
    let mut buf = String::new();
    let s = stdin();
    s.lock().read_line(&mut buf).ok();
    if buf.is_empty() {println!("No input"); exit(0)}
    buf.trim_right().split_whitespace().map(|t| t.parse::<T>().ok().unwrap()).collect()
  }

  pub fn mat<T: FromStr>(h: usize) -> Vec<Vec<T>> {
    let mut mat = vec![];
    for _ in 0 .. h {
      mat.push(vec());
    }
    mat
  }

  pub fn chars() -> Vec<char> {
    let mut buf = String::new();
    let s = stdin();
    s.lock().read_line(&mut buf).ok();
    if buf.is_empty() {println!("No input"); exit(0)}
    buf.trim_right().chars().collect()
  }
}

#[allow(dead_code)]
mod put {
  use std::string::*;

  pub fn vec<T: ToString>(vec: &Vec<T>, sep: &str) {
    let out = vec.iter().map(|e| e.to_string()).collect::<Vec<_>>().as_slice().join(sep);
    println!("{}", out);
  }

  pub fn mat<T: ToString>(mat: &Vec<Vec<T>>, sep: &str) {
    for v in mat {
      vec(v, sep);
    }
  }
}


Measured memory_bytes: 86280