#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <ctime>
#include <iterator>
#include <unordered_map>
#include <unordered_set>
#include <tuple> 
#include <memory>
#include <random>
#include <iterator>
#include <cassert>
using namespace std;

using LL = long long;
using ULL = unsigned long long;
constexpr int INF = 2147483647;
int dx[4] = { 0,1,0,-1 }, dy[4] = { 1,0,-1,0 };

//以下はC++の練習のための便利プログラム　あんまり便利じゃないけど…
//------------------------------------------便利型----------------------------------------------
//二次元の点
//TODO map,setのほうが楽かも
template <typename T>
struct Point2D
{
	T x;
	T y;

	friend ostream& operator<<(ostream& os, const Point2D<T>& point);

	bool operator < (const Point2D& right) const
	{
		return (this->x == right.x) ? (this->y > right.y) : (this->x > right.x);
	}
};

ostream& operator<<(ostream& os, const Point2D<int>& point)
{
	os << "(" << point.x << ',' << point.y << ")";
	return os;
}

//長くて読みづらかったのでエイリアステンプレートに
template<class T>
using StdAlloc = std::allocator<T>;

//簡単な２次元配列
//Tにboolを使うとおかしくなるので、bitset<1>を使うこと
template<class T>
class Array2D
{
public:
	struct Array2DAccessor
	{
		LL h;
		LL w;

		Array2DAccessor operator+(const Array2DAccessor& right) const {
			return{ h + right.h, w + right.w };
		}

		const static initializer_list<Array2DAccessor> UpDownLeftRight;
	};

public:
	Array2D(size_t height, size_t width, T init)
	{
		data = vector<vector<T>>(height, vector<T>(width, init));
	}

	//ループ　インデックスなし
	void ForEach(function<void(T&)> func)
	{
		for (auto& row : data) {
			for (T& column : row) {
				func(column);
			}
		}
	}

	//template<class FUNC>
	//void ForEach(FUNC func)
	//{
	//	for (auto& row : data) {
	//		for (T& column : row) {
	//			func(column);
	//		}
	//	}
	//}

	////ループ　インデックスあり、return条件あり
	////作ってみたけどめっちゃ使いにくい…
	//void ForEach(	//function<void(ULL, ULL, T&)> func,
	//				function<bool()> IsReturnAndClosureW = []() {return false; },
	//				function<bool()> IsReturnAndClosureH = []() {return false; }
	//			)
	//{
	//	for (ULL h = 0ULL; h < data.size(); h++) {
	//		if (IsReturnAndClosureW(h, w, data[h][w])) {
	//			continue;
	//		}
	//		for (ULL w = 0ULL; w < data[h].size(); w++) {
	//			if (IsReturnAndClosureH(h, w, data[h][w])) {
	//				break;
	//			}
	//			//func(h, w, data[h][w]);
	//		}
	//	}
	//}

	ULL GetHeight() 
	{
		return data.size();
	}

	ULL GetWidth()
	{
		return data.front().size();
	}

	template <class INDEX>
	vector<T>& operator[](INDEX indx)
	{
		return data[indx];
	}

	bool IsValid(const Array2DAccessor& indx) const
	{
		return 0LL <= indx.h && indx.h < (unsigned)data.size() && 0LL <= indx.w && indx.w < (unsigned)data.front().size();
	}

public:
	vector<vector<T>> data;
};

template<class T>
const initializer_list<typename Array2D<T>::Array2DAccessor> Array2D<T>::Array2DAccessor::UpDownLeftRight = { { 0LL, 1LL },{ 0LL, -1LL },{ -1LL, 0LL },{ 1LL, 0LL } };

//------------------------------------------便利型----------------------------------------------

//深さ優先探索
//再帰で実装するとStack Overflowになる…　Atcoderのスタックサイズはいくつ？
template<class Container, class Accessor>
tuple<ULL> DepthFirstSearch(
	Container& field,
	const Accessor& start,
	const initializer_list<Accessor>& searchDir,
	const function<void(const Accessor&, Container&)>& Work,
	const function<bool(const Accessor&, Container&)>& IsSearch
)
{
	tuple<ULL> info = make_tuple(0);	//探索の情報　てきとー
	queue<Accessor> taskQueue;
	taskQueue.push(start);

	while (!taskQueue.empty()) {
		const auto task = taskQueue.front();
		taskQueue.pop();

		//各方向へ探索
		for (const auto& df : searchDir) {
			auto indx = task + df;
			if (!IsSearch(indx, field)) {
				continue;
			}

			Work(indx, field);

			//探索に関する各種情報を更新
			get<0>(info)++;
			taskQueue.push(indx);
		}
	}

	return info;
}

//-----------------------------------------便利関数---------------------------------------------
//デバッグ用乱数
//random_device rnd;
//mt19937 mt(0);
//uniform_int_distribution<int> rand10_9(0, 10);
//for (unsigned i = 0; i < N; i++) {
//	numbers[i] = (long long)(rand10_9(mt) - 10);
//	//numbers[i] = 0;
//}

//部分文字列を作成
void MakeSubString(const string& str, vector<string>& subStrList, ULL maxLength) 
{
	for (int i = 0; i < str.size(); i++) {
		for (int j = i; j < str.size() && j - i <= maxLength; j++) {
			subStrList.emplace_back(str.substr(i, j - i + 1));
		}
	}
}

//コンテナに格納された要素がそれぞれいくつあるか
//処理時間は遅い
template< template<class T, class Allocator = StdAlloc<T>> class Container, class T >
void EachElementNum(const Container<T>& container, map<T, LL>& numberCounts)
{
	for (const auto& data : container) {
		numberCounts[data] = (numberCounts.count(data) == 0) ? 1 : ++numberCounts[data];
	}
}

template< template<class T, class Allocator = StdAlloc<T>> class Container, class T >
void EachElementNum(const Container<T>& container, vector<tuple<T, LL>>& numberCounts)
{
	map<T, LL> eachElementNum;
	EachElementNum(container, eachElementNum);
	for (const auto& info : eachElementNum) {
		numberCounts.emplace_back(make_tuple(info.first, info.second));
	}
}

//累積和
template< template<class T, class Allocator = StdAlloc<T>> class Container, class T >
void PrefixSum(Container<T>& c)
{
	for(auto now = begin(c) + 1, prev = begin(c); now != end(c); now++, prev++){
		*now += + *prev;
	}
}

//逆向きの累積和
template< template<class T, class Allocator = StdAlloc<T>> class Container, class T >
void ReversePrefixSum(Container<T>& c)
{
	for (auto now = rbegin(c) + 1, prev = rbegin(c); now != rend(c); now++, prev++) {
		*now += +*prev;
	}
}

//ユニークな要素数
template <class T>
unsigned long long UniqueElementNum(const vector<T>& data)
{
	auto copy = data;

	sort(begin(copy), end(copy));
	copy.erase(unique(begin(copy), end(copy)), end(copy));

	return copy.size();

	//set<unsigned long long>(begin(copy), end(copy)).size();	//１行版　マクロにしても良い
}

//ある閉区間の範囲内かどうか　やっぱり使いづらい
template <class T>
bool IsInClose(T x, T min, T max) {
	return (min <= x && x <= max);
}

//値が昇順になっているか
template<class T>
bool IsInOrder(const T& value1, const T& value2)
{
	return !(value2 < value1);
}

template<class T>
bool IsInOrder(const T& value1, const T& value2, const T& value3)
{
	return !(value2 < value1) && !(value3 < value2);
}

template<class T>
bool IsInOrder(const T& value1, const T& value2, const T& value3, const T& value4)
{
	return !(value2 < value1) && !(value3 < value2) && !(value4 < value3);
}

//値が範囲内かどうか
template<class T>
inline bool IsInRange(const T& min, const T& value, const T& max)
{
	return IsInOrder(min, value, max);
}

//最小公倍数 #TODO template化
long long gcd(long long a, long long b)
{
	long long c;

	if (a < b) {
		a += b; b = a - b; a -= b;
	}

	while (b != 0) {
		c = a % b;
		a = b;
		b = c;
	}

	return a;
}

//素数かどうか #TODO template化
bool IsPrime(ULL num)
{
	if (num < 2) return false;
	else if (num == 2) return true;
	else if (num % 2 == 0) return false; // 偶数はあらかじめ除く

	double sqrtNum = sqrt(num);
	for (ULL i = 3; i <= sqrtNum; i += 2)
	{
		if (num % i == 0)
		{
			// 素数ではない
			return false;
		}
	}

	// 素数である
	return true;
}

//素因数分解　試し割り（sqrt(n)まで）
//もっと早い方法もあるらしい…
void DecomposePrime(ULL n, vector<tuple<ULL, ULL>>& result)
{
	vector<ULL> primeList;

	// 割る数の初期値
	ULL a = 2;

	// √n ≧ a ( n ≧ a * a ) の間ループ処理
	while (n >= a * a) {
		// a で割り切れたら、a は素因数
		// そして、割られる数を a で割る
		// a で割り切れなかったら、 a を 1 増加させる
		if (n % a == 0) {
			primeList.emplace_back(a);
			n /= a;
		}
		else {
			a++;
		}
	}
	// 最後に残った n は素因数
	primeList.emplace_back(n);


	//素因数がそれぞれいくつあるかを調べる
	set<ULL> uniquePrimeList(begin(primeList), end(primeList));
	for (const auto& p : uniquePrimeList) {
		ULL primeNum = count(begin(primeList), end(primeList), p);
		result.emplace_back(make_tuple(p, primeNum));
	}
}

//n個からr個取り出す組み合わせの数
template <typename Type>
Type nCr(Type n, Type r) {
	Type ans = 1;
	for (Type i = n; i > n - r; --i) {
		ans = ans*i;
	}
	for (Type i = 1; i < r + 1; ++i) {
		ans = ans / i;
	}
	return ans;
}

//新しい方に対応したい場合、以下を参考にする
//コンテナへ要素を追加
//プリミティブ型など
template<class Num, class Container>
void InitContainer(Num num, Container& container) 
{
	container.resize(num);
	for (auto& d : container) {
		cin >> d;
	}
}

//コンテナへ要素を追加
//Container<Point2D<T>>で特殊化　読みにくい…　マクロにする？
template<class Num, template<class T, class Allocator = StdAlloc<T>> class Container, class T2>
void InitContainer(Num num, Container<Point2D<T2>>& firstContainer)
{
	firstContainer.resize(num);
	for (auto& d : firstContainer) {
		cin >> d.x >> d.y;
	}
}

//コンテナへ要素を追加　を呼び出す処理
template<class Num, class First, class... Rest>
void InitContainer(Num num, First& first, Rest&... rest)
{
	InitContainer(num, first);
	InitContainer(num, rest...);
}

//要素数Nと各要素をコンテナに格納
//コンテナを複数個取れるようにしてみた　テンプレートの練習
template <class Num, class... Container>
void InitNumAndContainer(Num& num, Container&... container)
{
	cin >> num;
	InitContainer(num, container...);
}

//コンテナの内容をstreamへ出力
//今はてきとーにcoutへ改行しながら　果たしてこの書き方は便利なのか…？
//#TODO 引数増やしたりオーバーロード増やしたり
template <template<class T, class Allocator = StdAlloc<T>> class Container, class T>
void ShowContainer(const Container<T>& container)
{
	ostream_iterator<T> outItr(cout, "\n");
	copy(container.begin(), container.end(), outItr);
	cout << endl;
}

//これはちょっと…
template <template<class T, class Allocator = StdAlloc<T>> class Container, class T>
void ShowContainer(const Container<T>& container, const function<T>& func)
{
	for (const auto& e : container) {
		func(e);
	}
	cout << endl;
}

//練習　行単位で文字列を読み込むためのイディオム
void InputStringForLine()
{
	constexpr size_t size{ 42 };
	vector<std::string> input;

	for (string buffer; getline(std::cin, buffer) && input.size() < size; input.push_back(buffer));
}

//-----------------------------------------便利関数---------------------------------------------
int main()
{
	ULL AB, BC, CA;
	cin >> AB >> BC >> CA;

	cout << AB * BC * 0.5;


	return 0;
}

Measured memory_bytes: 256