#include <bits/stdc++.h>
// #include <atcoder/all>
#define REP(i, n) for (int i = 0; i < (int)(n); i++)
#define REPLL(i, n) for (ll i = 0; i < (ll)(n); i++)
using namespace std;
// using namespace atcoder;
template<class T>inline bool chmax(T &a, const T &b){if(a < b){a = b; return 1;}return 0;}
template<class T>inline bool chmin(T &a, const T &b){if(a > b){a = b; return 1;}return 0;}
typedef long long ll;
// INF<int>
// INF<ll>
template<typename T>
const auto INF = numeric_limits<T>::max();
/////////////////////////////////////////////////////////////////////
// graph-template
// Graph<int> g(V); // int型、頂点数Vのグラフ
// Graph<ll> g(V); // ll型、頂点数Vのグラフ
// g.add_directed_edge(f, t, c); // 有向グラフの場合
// g.add_edge(f, t, c); // 無向グラフの場合
// g.read(M); // 辺の数M、0-indexed必要、重みなし、無向グラフの場合
// g.read(M, 0, true); // 辺の数M、0-indexed不要、重みあり、無向グラフの場合
// g.read(M, 0, true, true); // 辺の数M、0-indexed不要、重みあり、有向グラフの場合
// int N = g.size();
// Edges<int> es; es.emplace_back(from, to, cost); // Edges<int>で受け取る場合
/////////////////////////////////////////////////////////////////////
template<typename T = int>
struct Edge{
  int from, to; T cost; int idx;
  Edge() = default;
  Edge(int from, int to, T cost = 1, int idx = -1) : from(from), to(to), cost(cost), idx(idx){}
  operator int() const {return to;}
};
template<typename T = int>
struct Graph{
  vector<vector<Edge<T> > > g;
  int es;
  Graph() = default;
  explicit Graph(int n) : g(n), es(0){}
  size_t size() const{
    return g.size();
  }
  void add_directed_edge(int from, int to, T cost = 1){ // 有向グラフ
    g[from].emplace_back(from, to, cost, es++);
  }
  void add_edge(int from, int to, T cost = 1){ // 無向グラフ
    g[from].emplace_back(from, to, cost, es);
    g[to].emplace_back(to, from, cost, es++);
  }
  void read(int M, int padding = -1, bool weighted = false, bool directed = false) {
    for(int i = 0; i < M; i++) {
      int a, b;
      cin >> a >> b;
      a += padding;
      b += padding;
      T c = T(1);
      if(weighted) cin >> c;
      if(directed) add_directed_edge(a, b, c);
      else add_edge(a, b, c);
    }
  }
};
template<typename T = int>
using Edges = vector<Edge<T> >;
template<typename T = int>
using Matrix = vector<vector<T> >;
/////////////////////////////////////////////////////////////////////
// BFS（幅優先探索）
// vector<int> dist = bfs(g, s);
// for(auto &i : bfs(g, s)){cout << i << endl;} // 最短距離だけを表示する場合 
/////////////////////////////////////////////////////////////////////
template<typename T>
vector<T> bfs(const Graph<T> &g, int s) {
  T max_cost = 0, beet = 0;
  for(auto &es : g.g) {
    for(auto &e : es) max_cost = max(max_cost, e.cost);
  }
  ++max_cost;
  const auto INF = numeric_limits<T>::max();
  vector<T> dist(g.size(), INF);
  vector<queue<int> > ques(max_cost + 1);
  dist[s] = 0;
  ques[0].emplace(s);
  for(T cost = 0; cost <= beet; cost++) {
    auto &que = ques[cost % max_cost];
    while(!que.empty()) {
      int idx = que.front();
      que.pop();
      if(dist[idx] < cost) continue;
      for(auto &e : g.g[idx]) {
        auto next_cost = cost + e.cost;
        if(dist[e.to] <= next_cost) continue;;
        dist[e.to] = next_cost;
        beet = max(beet, dist[e.to]);
        ques[dist[e.to] % max_cost].emplace(e.to);
      }
    }
  }
  return dist;
}
/////////////////////////////////////////////////////////////////////
// ダイクストラ法（Dijkstra）法(単一始点最短路・負の辺がない場合)
// ShortestPath<int> SP = dijkstra(g, s); // グラフgと始点sを渡すと、全頂点への最短距離を返す
// ShortestPath<ll> SP = dijkstra(g, s);
// for(auto &i : dijkstra(g, r).dist){cout << i << endl;} // 最短距離のみを出力する 
/////////////////////////////////////////////////////////////////////
template<typename T>
struct ShortestPath{
  vector<T> dist;
  vector<int> from, id;
};
template<typename T>
ShortestPath<T> dijkstra(const Graph<T> &g, int s){
  const auto INF = numeric_limits<T>::max();
  vector<T> dist(g.size(), INF);
  vector<int> from(g.size(), -1), id(g.size(), -1);
  using Pi = pair<T, int>;
  priority_queue<Pi, vector<Pi>, greater<> > que;
  dist[s] = 0;
  que.emplace(dist[s], s);
  while(!que.empty()){
    T cost;
    int idx;
    tie(cost, idx) = que.top();
    que.pop();
    if(dist[idx] < cost) continue;
    for(auto &e : g.g[idx]){
      auto next_cost = cost + e.cost;
      if(dist[e.to] <= next_cost) continue;
      dist[e.to] = next_cost;
      from[e.to] = idx;
      id[e.to] = e.idx;
      que.emplace(dist[e.to], e.to);
    }
  }
  return {dist, from, id};
}
/////////////////////////////////////////////////////////////////////
// ベルマンフォード（Bellman-Ford）法(単一始点最短路・負の辺を含む場合)
// Edges<T>を受け取る（Graph<T>ではない。）
// Edges<int> es; es.emplace_back(from, to, cost);
// vector<int> dist = bellman_ford(es, V, s);
// vector<ll> dist = bellman_ford(es, V, s);
// 到達できないときはINF<T>を返す。負閉路を検出したときは、空の数列を返す。
// if(dist.empty()){ } // 負閉路を検出した場合の処理
/////////////////////////////////////////////////////////////////////
template<typename T>
vector<T> bellman_ford(const Edges<T> &edges, int V, int s) {
  const auto INF = numeric_limits<T>::max();
  vector<T> dist(V, INF);
  dist[s] = 0;
  for(int i = 0; i < V - 1; i++){
    for(auto &e : edges){
      if(dist[e.from] == INF) continue;
      dist[e.to] = min(dist[e.to], dist[e.from] + e.cost);
    }
  }
  for(auto &e : edges){
    if(dist[e.from] == INF) continue;
    if(dist[e.from] + e.cost < dist[e.to]) return vector<T>();
  }
  return dist;
}
/////////////////////////////////////////////////////////////////////
// ワーシャルフロイド（WarshallFloyd）法（全点最短経路）
// 隣接行列で表されるグラフの全点間最短路を求めるアルゴリズム。負辺があっても動作する。
// 負閉路が存在する場合はそれも検出する(ある頂点kからkへの最短路が負ならば負閉路が存在)。
// Matrix<int> mat(V, vector<int>(V, INF<int>)); REP(i, V) mat[i][i] = 0; // 初期化
// mat[i][j] = cost;
// warshall_floyd(mat, INF<int>);
/////////////////////////////////////////////////////////////////////
template<typename T>
void warshall_floyd(Matrix<T> &g, T INF) {
  for(int k = 0; k < g.size(); k++) {
    for(int i = 0; i < g.size(); i++) {
      for(int j = 0; j < g.size(); j++) {
        if(g[i][k] == INF || g[k][j] == INF) continue;
        g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
      }
    }
  }
}
/////////////////////////////////////////////////////////////////////
// Divisor（約数列挙）
// vector<ll> div = divisor(N);
/////////////////////////////////////////////////////////////////////
template<typename T = ll>
vector<T> divisor(T n) {
  vector<T> ret;
  for(T i = 1; i * i <= n; i++) {
    if(n % i == 0) {
      ret.push_back(i);
      if(i * i != n) ret.push_back(n / i);
    }
  }
  sort(begin(ret), end(ret));
  return (ret);
}
/////////////////////////////////////////////////////////////////////
// isPrime（素数判定）
// bool ip = isPrime(N); // Nが素数ならtrue、素数でなければfalseを返す
/////////////////////////////////////////////////////////////////////
template<typename T = ll>
bool isPrime(T n){
  if(n <= 1) return false;
  if(n == 2) return true;
  if(n % 2 == 0) return false;
  for(T i = 3; i*i <= n; i += 2){
    if(n % i == 0) return false;
  }
  return true;
}
/////////////////////////////////////////////////////////////////////
// PrimeFactor（素因数分解）
// auto MP = Prime_Factor(N);
// for(auto i : MP){
//   cout << i.first << " " << i.second << endl;
// }
/////////////////////////////////////////////////////////////////////
template<typename T = ll>
map<T, T> Prime_Factor(T n){
  map<T, T> mp; T cnt = 0;
  while(n%2 == 0){
    n /= 2; cnt++;
  }
  if(cnt != 0) mp[2] = cnt;
  for(T i = 3; i*i <= n; i += 2){
    cnt = 0;
    while(n%i == 0){
      n /= i; cnt++;
    }
    if(cnt != 0) mp[i] = cnt;
  }
  if(n != 1) mp[n] = 1;
  return mp;
}
/////////////////////////////////////////////////////////////////////
// ModInt（mod Mでの四則演算を行う構造体）
// ModInt a = N;
/////////////////////////////////////////////////////////////////////
template<int mod>
struct ModInt {
  int x;
  ModInt() : x(0) {}
  ModInt(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}
  ModInt &operator+=(const ModInt &p) {
    if((x += p.x) >= mod) x -= mod;
    return *this;
  }
  ModInt &operator-=(const ModInt &p) {
    if((x += mod - p.x) >= mod) x -= mod;
    return *this;
  }
  ModInt &operator*=(const ModInt &p) {
    x = (int) (1LL * x * p.x % mod);
    return *this;
  }
  ModInt &operator/=(const ModInt &p) {
    *this *= p.inverse();
    return *this;
  }
  ModInt operator-() const { return ModInt(-x); }
  ModInt operator+(const ModInt &p) const { return ModInt(*this) += p; }
  ModInt operator-(const ModInt &p) const { return ModInt(*this) -= p; }
  ModInt operator*(const ModInt &p) const { return ModInt(*this) *= p; }
  ModInt operator/(const ModInt &p) const { return ModInt(*this) /= p; }
  bool operator==(const ModInt &p) const { return x == p.x; }
  bool operator!=(const ModInt &p) const { return x != p.x; }
  ModInt inverse() const {
    int a = x, b = mod, u = 1, v = 0, t;
    while(b > 0) {
      t = a / b;
      swap(a -= t * b, b);
      swap(u -= t * v, v);
    }
    return ModInt(u);
  }
  ModInt pow(int64_t n) const {
    ModInt ret(1), mul(x);
    while(n > 0) {
      if(n & 1) ret *= mul;
      mul *= mul;
      n >>= 1;
    }
    return ret;
  }
  friend ostream &operator<<(ostream &os, const ModInt &p) {
    return os << p.x;
  }
  friend istream &operator>>(istream &is, ModInt &a) {
    int64_t t;
    is >> t;
    a = ModInt< mod >(t);
    return (is);
  }
  static int get_mod() { return mod; }
};
/////////////////////////////////////////////////////////////////////
// 初期設定
/////////////////////////////////////////////////////////////////////
const int mod = 1000000007;
using modint = ModInt<mod>;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  ////////////////////////////////// コード //////////////////////////////////
  ll N; cin >> N;
  ll ans = N*(N-1)/2;
  cout << ans << endl;
}

Measured memory_bytes: 3620