#include <bits/stdc++.h>
#define REP(i,n) for(int i=0;i<(n);++i)
#define ALL(v)   (v).begin(),(v).end()
#define debug(x) cerr<<#x<<": "<<(x)<<endl
using namespace std;
using llong = long long;
using vi    = vector<int>;
using vvi   = vector<vi >;
using vvvi  = vector<vvi >;
using pii   = pair<int,int>;
constexpr int    INF=1e9;
constexpr double EPS=1e-9;
constexpr int    MOD=1e9+7;
template<class Type>
void line(const Type &a){int cnt=0;for(const auto &elem:a){cerr<<(cnt++?' ':'>');cerr<<elem;}cerr<<endl;}

/*遅延評価セグメント木*/
template<typename T,typename U=T>
class LazySegmentTree{
	using FX=function<T(T,T)>;
	using FA=function<T(T,U)>;
	using FM=function<U(U,U)>;
	using FP=function<U(U,int)>;
	int n,offset;	//n:=(数列の数（2の階乗数)),offset:=(オフセット（n-1）).
	const T ex;		//ex:=(モノイドXの単位元).
	const U em;		//em:=(モノイドMの単位元).
	vector<T> node;
	vector<U> lazy;
	const FX fx;	//fx:=(X ✕ X->X).　比較用.
	const FA fa;	//fa:=(X ✕ M->X). 更新用.
	const FM fm;	//fa:=(M ✕ M->M). 遅延分比較用（基本fxと同じ）.
	const FP fp;	//fp:=(写像).
	
	int bufsize(int n_){//木に必要なノード数を返す.
		n=1;
		while(n<n_) n<<=1;
		offset=n-1;
		return 2*n-1;
	}
	void build(int l=0,int r=-1){//木を構築する. O(N).
		if(r<0) r=n;
		l+=offset, r+=offset;
		do{
			l=(l-1)/2, r=(r-1)/2;
			for(int i=l;i<r;++i) node[i]=fx(node[2*i+1],node[2*i+2]);
		}while(l>0);
	}
	void eval(int k,int l,int r){//k番目のノードについて遅延評価する.
		if(lazy[k]==em) return;
		node[k]=fa(node[k],fp(lazy[k],r-l));
		if(k<offset){//葉でなければ子に伝搬.
			lazy[2*k+1]=fm(lazy[2*k+1],lazy[k]);
			lazy[2*k+2]=fm(lazy[2*k+2],lazy[k]);
		}
		lazy[k]=em;
	}
	
public:
	/*constructor.*/
	LazySegmentTree(int n_,T ex_,U em_,FX fx_,FA fa_,FM fm_,FP fp_)
	:ex(ex_),em(em_),fx(fx_),fa(fa_),fm(fm_),fp(fp_){
		int sz=bufsize(n_);
		node.assign(sz,ex), lazy.assign(sz,em);
	}
	LazySegmentTree(const vector<T> &v_,T ex_,U em_,FX fx_,FA fa_,FM fm_,FP fp_)
	:ex(ex_),em(em_),fx(fx_),fa(fa_),fm(fm_),fp(fp_){
		int sz=bufsize(v_.size());
		node.assign(sz,ex), lazy.assign(sz,em);
		copy(ALL(v_),node.begin()+offset);//<algorithm>.
		build();
	}
	
	void init(){fill(ALL(node),ex), fill(ALL(lazy),em);}//初期化する.
	void update(int a,int b,T x,int k=0,int l=0,int r=-1){//[a,b)間の要素をaに更新する. O(log N).
		if(r<0) r=n;
		eval(k,l,r);
		if(b<=l or r<=a) return;
		if(a<=l and r<=b){
			lazy[k]=fm(lazy[k],x);
			eval(k,l,r);
		}else{
			update(a,b,x,2*k+1,l,(l+r)/2);
			update(a,b,x,2*k+2,(l+r)/2,r);
			node[k]=fx(node[2*k+1],node[2*k+2]);
		}
	}
	/*[a,b)間のクエリ結果を返す. O(log N).
	（後ろの3つの引数は計算の簡単のための引数. kは節点の番号,lとrはその節点が[l,r)に対応づいているかを表す. したがって,外からはquery(a,b,0,0,n)と呼ぶ.）*/
	T query(int a,int b,int k=0,int l=0,int r=-1){
		if(r<0) r=n;
		eval(k,l,r);
		if(b<=l or r<=a) return ex;			//[a,b)と[l,r)が交差しなければex.
		if(a<=l and r<=b) return node[k];	//[a,b)が[l,r)を完全に含んでいれば,この節点の値.
		T vl=query(a,b,2*k+1,l,(l+r)/2);
		T vr=query(a,b,2*k+2,(l+r)/2,r);
		return fx(vl,vr);					//交差する場合,2つの子をクエリ処理して比較.
	}
	
	typename vector<T>::reference operator[](int i){return node[i+offset];}
};

int main(){
	int n,q;
	cin>>n>>q;
	
	using Type=int;
	auto fx=[](Type a,Type b)->Type{return (a<b?a:b);};//比較用.
	auto fa=[](Type a,Type b)->Type{return (a<b?a:b);};//更新用.
	auto fm=[](Type a,Type b)->Type{return (a<b?a:b);};//遅延分比較用（基本fxと同じ）.
	auto fp=[](Type a,int len)->Type{return a;};//写像.
	LazySegmentTree<Type> y(n,n-1,n-1,fx,fa,fm,fp);//それぞれのラインで最もレーンが近い位置.
	LazySegmentTree<Type> x(n,n-1,n-1,fx,fa,fm,fp);
	
	llong ans=(llong)(n-2)*(n-2);
	REP(i,q){
		int t,p;
		cin>>t>>p;
		p--;
		
		if(t==1){
			int u=1, l=x.query(p,p+1);
			//debug(l);
			ans-=l-u;
			y.update(0,l,p);
		}else{
			int l=1, r=y.query(p,p+1);
			//debug(r);
			ans-=r-l;
			x.update(0,r,p);
		}
		//debug(ans);
	}
	
	cout<<ans<<endl;
	return 0;
}

Measured memory_bytes: 11472