import java.util.ArrayList;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;

public class C343 {

	static long LongestIncreasingSubsequenceLength(long A[], int size) {
		// Add boundary case, when array size is one
		// pair3[] tailTable = new pair3[size];
		ConcurrentSkipListSet<pair3> tailTable = new ConcurrentSkipListSet<pair3>();
		pair3 pair = new pair3();
		pair.max = A[0];
		pair.volume = A[0];
		tailTable.add(pair);
		long max = A[0]* 1L;
		long current = 0;
		for (int i = 1; i < size; i++) {
		//	System.out.println(i);
			pair3 p = new pair3();
			p.max = 0;
			p.volume = A[i];

			pair3 index = tailTable.higher(p);
			//System.out.println();
		//	System.out.println();
			if (index == null) {
			//	System.out.println("??");
				pair3 last = tailTable.last();
	            current = last.max + A[i];
	            p.max = current;
	            tailTable.add(p);
	            max = Math.max(max,current);
				// new smallest value
			}else{
				if(index.equals(tailTable.first())){
					current =0;
				}
				else{

					current= tailTable.floor(p).max;
				}
				current+=A[i];
				p.max = current;
				tailTable.add(p);

		            Iterator<pair3> itr = tailTable.tailSet(p).iterator();
		            itr.next();
		            while(itr.hasNext()){
		            	pair3 p3 = itr.next();
		            	if(p3.max<=current){
		            		tailTable.remove(p3);
		            	}
		            }

		            max = Math.max(current,max);
		        }
			}

		return max;
	}

	public static void main(String args[]) throws Exception {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		long[] array = new long[n];
		for (int i = 0; i < n; i++) {
			long r = sc.nextLong();
			long h = sc.nextLong();
			long volume =r*r* h;
			array[i] = volume;
		}
		double val = LongestIncreasingSubsequenceLength(array, n)* Math.PI;
		System.out.printf("%.14f\n",val);

	}
}

class pair3 implements Comparable {
	long volume;
	long max;

	@Override
	public int compareTo(Object o) {
		long v = ((pair3) o).volume;
		return ((Long) volume).compareTo(v);
	}

}


import java.util.ArrayList;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;

public class D343 {

	static long LongestIncreasingSubsequenceLength(long A[], int size) {
		// Add boundary case, when array size is one
		// pair3[] tailTable = new pair3[size];
		ConcurrentSkipListSet<pair3> tailTable = new ConcurrentSkipListSet<pair3>();
		pair3 pair = new pair3();
		pair.max = A[0];
		pair.volume = A[0];
		tailTable.add(pair);
		long max = A[0]* 1L;
		long current = 0;
		for (int i = 1; i < size; i++) {
		//	System.out.println(i);
			pair3 p = new pair3();
			p.max = 0;
			p.volume = A[i];

			pair3 index = tailTable.higher(p);
			//System.out.println();
		//	System.out.println();
			if (index == null) {
			//	System.out.println("??");
				pair3 last = tailTable.last();
	            current = last.max + A[i];
	            p.max = current;
	            tailTable.add(p);
	            max = Math.max(max,current);
				// new smallest value
			}else{
				if( A[i] <= tailTable.first().volume){
					current =0;
				}
				else{
					//if(tailTable.lower(p)==null){
					//	System.out.println(A[i] + " " + p.max + " " + p.volume );
					//	System.out.println(tailTable.first().volume + " " + tailTable.first().max);
					//}
					current= tailTable.lower(p).max;
				}
				current+=A[i];
				p.max = current;
				tailTable.add(p);

		            Iterator<pair3> itr = tailTable.tailSet(p).iterator();
		            itr.next();
		            while(itr.hasNext()){
		            	pair3 p3 = itr.next();
		            	if(p3.max<=current){
		            		tailTable.remove(p3);
		            	}
		            	else{
		            		break;
		            	}
		            }

		            max = Math.max(current,max);
		        }
			}

		return max;
	}

	public static void main(String args[]) throws Exception {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		long[] array = new long[n];
		for (int i = 0; i < n; i++) {
			long r = sc.nextLong();
			long h = sc.nextLong();
			long volume =r*r* h;
			array[i] = volume;
		}
		double val = LongestIncreasingSubsequenceLength(array, n)* Math.PI;
		System.out.printf("%.14f\n",val);

	}
}
	class pair3 implements Comparable {
		long volume;
		long max;

		@Override
		public int compareTo(Object o) {
			long v = ((pair3) o).volume;
			return ((Long) volume).compareTo(v);
		}
}


import java.util.ArrayList;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;

public class C343 {

	static long LongestIncreasingSubsequenceLength(long A[], int size) {
		// Add boundary case, when array size is one
		// pair3[] tailTable = new pair3[size];
		ConcurrentSkipListSet<pair3> tailTable = new ConcurrentSkipListSet<pair3>();

		pair3 pair = new pair3();
		pair.max = A[0];
		pair.volume = A[0];
		tailTable.add(pair);
		long max = A[0]* 1L;
		for (int i = 1; i < size; i++) {
			if (A[i] < tailTable.first().volume) {
				// new smallest value
				pair3 p = new pair3();
				p.max = A[i];
				p.volume = A[i];
				Iterator<pair3> itr = tailTable.iterator();
//				while (itr.hasNext()) {
//					pair3 pr = itr.next();
//					if (pr.max <= p.max) {
//						tailTable.remove(pr);
//					}
//				}
				tailTable.add(p);
				max = Math.max(p.max, max);
			} else {
				pair3 last = tailTable.last();
				if (A[i] > last.volume) {
					// A[i] wants to extend largest subsequence
					if (A[i] != last.volume) {
						pair3 p = new pair3();
						p.volume = A[i];
						p.max = A[i] + last.max;
						tailTable.add(p);
						max = Math.max(p.max, max);
					}
				}

				else {
					// A[i] wants to be current end candidate of an existing
					// subsequence. It will replace ceil value in tailTable
					pair3 p = new pair3();
					p.volume = A[i];
					p.max = 0;
					pair3 index = tailTable.higher(p);
					pair3 IM = tailTable.lower(index);
					p.max = IM.max + A[i];
					ConcurrentSkipListSet<pair3> set = (ConcurrentSkipListSet<pair3>) tailTable.tailSet(index);
					Iterator<pair3> itr = set.iterator();
					while (itr.hasNext()) {
						pair3 pr = itr.next();
						if (pr.max <= p.max) {
							tailTable.remove(pr);
						}
					}
					tailTable.add(p);
					max = Math.max(p.max, max);
				}
			}

		}
		return max;
	}

	public static void main(String args[]) throws Exception {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		long[] array = new long[n];
		for (int i = 0; i < n; i++) {
			long r = sc.nextLong();
			long h = sc.nextLong();
			long volume =r*r* h;
			array[i] = volume;
		}
		double val = LongestIncreasingSubsequenceLength(array, n)* Math.PI;
		System.out.printf("%.14lf\n",val);

	}
}

class pair3 implements Comparable {
	long volume;
	long max;

	@Override
	public int compareTo(Object o) {
		long v = ((pair3) o).volume;
		return ((Long) volume).compareTo(v);
	}

}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Scanner;

public class C343 {

	static pair3 [] dp;
    static int index;
	public static double lengthOfLIS(double[] nums) {
        if(nums.length==0){
            return 0;
        }
        dp = new pair3[nums.length+1];
       // Arrays.fill(dp, Integer.MIN_VALUE);
        int max = Integer.MIN_VALUE;
        double val = Double.MIN_VALUE;
        for(int i =0; i<nums.length; i++){
        	pair3 p = getLongest(i, nums);
        	if(p.value>val){
        		val = p.value;
        	}
//        	int value= getLongest(i, nums);
//        	if(value>=max) index = i;
           //max = Math.max(max, value);
       }
        return val;
    }

    static pair3 getLongest(int index, double [] nums){
        if(index==0){
        	pair3 p = new pair3();
        	p.length = 1;
        	p.value = nums[index];
            return p;
        }
        if(dp[index]!=null){
            return dp[index];
        }

        int max = 1;
        double value = Double.MIN_VALUE;
        for(int i =index-1; i>=0; i--){
            if(nums[i]<nums[index]){
                pair3 p  = getLongest(i, nums);
                if(p.length>max || (p.length==max && p.value + nums[index]>value)){
                	max = p.length;
                	value = p.value + nums[index];
                }

            }
        }
        pair3 sol = new pair3();
        sol.length = max;
        sol.value = value;
        return dp[index] = sol;
    }
//	static double value = 0;
//    static void getNumber(int index, double [] nums){
//       System.out.println(index);
//    	if(index==0){
//        	//System.out.println("here");
//        	value+=nums[0];
//            return;
//        }
//
//        int max = 1;
//        for(int i =index-1; i>=0; i--){
//            if(nums[i]<nums[index]){
//                int longest = getLongest(i, nums);
//                if(longest+1==dp[index]){
//                	value+=nums[index];
//                	getNumber(i, nums);
//                	return;
//                }
//            //    max = Math.max(max, 1 + longest);
//            }
//        }
//       // return dp[index] = max;
//    }

	public static void main(String args[] ) throws Exception {
		Scanner sc = new Scanner(System.in);
		//BufferedReader bf  = new BufferedReader(new InputStreamReader(System.in));
		int n = sc.nextInt();
		double [] array = new double [n];
		for(int i =0; i<n; i++){
			int r = sc.nextInt();
			int h = sc.nextInt();
			double volume = Math.pow(r, 2) * h * 3.14159265359;
			//System.out.print("v " + volume + " ");
			array[i]= volume;
		}
		//System.out.println("");
		System.out.println(lengthOfLIS(array));
		// getNumber(index, array);
		// System.out.println(nn  + " "+ value);
	}
}
class pair3{
	int length;
	double value;
}


import java.util.ArrayList;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;

public class C343 {

	static long LongestIncreasingSubsequenceLength(long A[], int size) {
		// Add boundary case, when array size is one
		// pair3[] tailTable = new pair3[size];
		ConcurrentSkipListSet<pair3> tailTable = new ConcurrentSkipListSet<pair3>();

		pair3 pair = new pair3();
		pair.max = A[0];
		pair.volume = A[0];
		tailTable.add(pair);
		long max = A[0];
		for (int i = 1; i < size; i++) {
			if (A[i] < tailTable.first().volume) {
				// new smallest value
				pair3 p = new pair3();
				p.max = A[i];
				p.volume = A[i];
				ConcurrentSkipListSet<pair3> set = (ConcurrentSkipListSet<pair3>) tailTable.tailSet(tailTable.first());
				Iterator<pair3> itr = set.iterator();
				while (itr.hasNext()) {
					pair3 pr = itr.next();
					if (pr.max <= p.max) {
						tailTable.remove(pr);
					}
				}
				tailTable.add(p);
				max = Math.max(p.max, max);
			} else {

				pair3 last = tailTable.last();
				if (A[i] >= last.volume) {
					// A[i] wants to extend largest subsequence
					if(A[i]!=last.volume){
					pair3 p = new pair3();
					p.volume = A[i];
					p.max = A[i] + last.max;
					tailTable.add(p);
					max = Math.max(p.max, max);
					}
				}

				else {
					// A[i] wants to be current end candidate of an existing
					// subsequence. It will replace ceil value in tailTable
					pair3 p = new pair3();
					p.volume = A[i];
					p.max = 0;
					pair3 index = tailTable.higher(p);
					pair3 IM = tailTable.lower(index);
						p.max = IM.max + A[i];
						ConcurrentSkipListSet<pair3> set = (ConcurrentSkipListSet<pair3>) tailTable.tailSet(index);

						Iterator<pair3> itr = set.iterator();

						while (itr.hasNext()) {
							pair3 pr = itr.next();
							if (pr.max <= p.max) {
								tailTable.remove(pr);
							}
						}

					tailTable.add(p);
					max = Math.max(p.max, max);
				}
			}

		}
		return max;
	}

	public static void main(String args[]) throws Exception {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		long[] array = new long[n];
		for (int i = 0; i < n; i++) {
			long r = sc.nextLong();
			long h = sc.nextLong();
			long volume = r*r*h;
			array[i] = volume;
		}
		double pi = 3.14159265359;
		long val = LongestIncreasingSubsequenceLength(array, n);
		System.out.println(pi * val);
	}
}

class pair3 implements Comparable {
	long volume;
	long max;

	@Override
	public int compareTo(Object o) {
		long v = ((pair3) o).volume;
		return ((Long) volume).compareTo(v);
	}

}


import java.util.*;
import java.io.*;

public class C {

	FastIO scan = new FastIO(new BufferedOutputStream(System.out));

	public class SegNode{
		int left , right;
		double val = 0.0;
		public SegNode(int left , int right){
			this.left = left;
			this.right = right;
		}
		public int mid(){
			return (left + right) / 2;
		}
	}

	SegNode tree[];

	public void build(int left , int right , int idx){
		tree[idx] = new SegNode(left , right);
		if(left == right)
			return;
		int mid = tree[idx].mid();
		build(left , mid , idx << 1);
		build(mid + 1 , right , idx << 1 | 1);
	}

	public void pushup(int idx){
		tree[idx].val = Math.max(tree[idx << 1].val , tree[idx << 1 | 1].val);
	}

	public void update(int index , int idx , double val){
		if(tree[idx].left == tree[idx].right){
			tree[idx].val = val;
			return;
		}
		int mid = tree[idx].mid();
		if(index <= mid){
			update(index , idx << 1 , val);
		}else{
			update(index , idx << 1 | 1 , val);
		}
		pushup(idx);
	}

	public double query(int left , int right , int idx){
		if(left == tree[idx].left && right == tree[idx].right){
			return tree[idx].val;
		}
		int mid = tree[idx].mid();
		if(right <= mid){
			return query(left , right , idx << 1);
		}else if(left > mid){
			return query(left , right , idx << 1 | 1);
		}else {
			return Math.max(query(left , mid , idx << 1) , query(mid + 1 , right , idx << 1 | 1));
		}
	}

	public void run(){
		int n = scan.nextInt();
		TreeMap<Double,Integer> map = new TreeMap<Double,Integer>();
		TreeSet<Double> set = new TreeSet<Double>();
		int r[] = new int[n];
		int h[] = new int[n];
		double c[] = new double[n];
		for(int i = 0 ; i < n ; i ++){
			r[i] = scan.nextInt();
			h[i] = scan.nextInt();
			c[i] = Math.PI * r[i] * r[i] * h[i];
			set.add(c[i]);
		}
		int cnt = 1;
		for(double i : set){
			map.put(i , cnt ++);
		}
		tree = new SegNode[n * 4];
		build(1 , n , 1);
		double dp[] = new double[n];
		dp[0] = c[0];
		double ans = dp[0];
		update(map.get(c[0]) , 1 , dp[0]);
		for(int i = 1 ; i < n ; i ++){
			double cur = c[i];
			int rr = map.get(c[i]);
			if(rr == 1){
				dp[i] = c[i];
			}else{
				double temp = query(1 , rr - 1 , 1);
				//System.out.println(c[i] +" "+temp);
				dp[i] = c[i] + temp;
				update(rr , 1 , dp[i]);
			}
			ans = Math.max(ans , dp[i]);
		}
		System.out.println(ans);

	}

	public static void main(String args[]){
		new C().run();
	}

	class FastIO extends PrintWriter {
		BufferedReader br;
		StringTokenizer st;

		public FastIO(OutputStream psOut) {
			super(psOut);
			try {
				br = new BufferedReader(new FileReader(new File("in")));
			} catch (Exception e) {
				br = new BufferedReader(new InputStreamReader(System.in));
			}
			eat("");
		}

		public void eat(String s) {
			st = new StringTokenizer(s);
		}

		public String nextLine() {
			try {
				return br.readLine();
			} catch (IOException e) {
				throw new IOError(e);
			}
		}

		public boolean hasNext() {
			while (!st.hasMoreTokens()) {
				String s = nextLine();
				if (s == null)
					return false;
				eat(s);
			}
			return true;
		}

		public String next() {
			hasNext();
			return st.nextToken();
		}

		public int nextInt() {
			return Integer.parseInt(next());
		}

		public long nextLong() {
			return Long.parseLong(next());
		}

		public double nextDouble() {
			return Double.parseDouble(next());
		}

		public void println(Object... o) {
			for (Object i : o)
				print(i + " ");
			println();
		}
	}

}


import java.util.*;
import java.io.*;

public class C {

	FastIO scan = new FastIO(new BufferedOutputStream(System.out));

	public class SegNode{
		int left , right;
		double val = 0.0;
		public SegNode(int left , int right){
			this.left = left;
			this.right = right;
		}
		public int mid(){
			return (left + right) / 2;
		}
	}

	SegNode tree[];

	public void build(int left , int right , int idx){
		tree[idx] = new SegNode(left , right);
		if(left == right)
			return;
		int mid = tree[idx].mid();
		build(left , mid , idx << 1);
		build(mid + 1 , right , idx << 1 | 1);
	}

	public void pushup(int idx){
		tree[idx].val = Math.max(tree[idx << 1].val , tree[idx << 1 | 1].val);
	}

	public void update(int index , int idx , double val){
		if(tree[idx].left == tree[idx].right){
			tree[idx].val = val;
			return;
		}
		int mid = tree[idx].mid();
		if(index <= mid){
			update(index , idx << 1 , val);
		}else{
			update(index , idx << 1 | 1 , val);
		}
		pushup(idx);
	}

	public double query(int left , int right , int idx){
		if(left == tree[idx].left && right == tree[idx].right){
			return tree[idx].val;
		}
		int mid = tree[idx].mid();
		if(right <= mid){
			return query(left , right , idx << 1);
		}else if(left > mid){
			return query(left , right , idx << 1 | 1);
		}else {
			return Math.max(query(left , mid , idx << 1) , query(mid + 1 , right , idx << 1 | 1));
		}
	}

	public void run(){
		int n = scan.nextInt();
		TreeMap<Double,Integer> map = new TreeMap<Double,Integer>();
		TreeSet<Double> set = new TreeSet<Double>();
		int r[] = new int[n];
		int h[] = new int[n];
		double c[] = new double[n];
		for(int i = 0 ; i < n ; i ++){
			r[i] = scan.nextInt();
			h[i] = scan.nextInt();
			c[i] = (double)r[i] * r[i] * h[i];
			set.add(c[i]);
		}
		int cnt = 1;
		for(double i : set){
			map.put(i , cnt ++);
		}
		tree = new SegNode[n * 4];
		build(1 , n , 1);
		double dp[] = new double[n];
		dp[0] = c[0];
		double ans = dp[0];
		update(map.get(c[0]) , 1 , dp[0]);
		for(int i = 1 ; i < n ; i ++){
			double cur = c[i];
			int rr = map.get(c[i]);
			if(rr == 1){
				dp[i] = c[i];
			}else{
				double temp = query(1 , rr - 1 , 1);
				//System.out.println(c[i] +" "+temp);
				dp[i] = c[i] + temp;
				update(rr , 1 , dp[i]);
			}
			ans = Math.max(ans , dp[i]);
		}
		System.out.println(ans * Math.PI);

	}

	public static void main(String args[]){
		new C().run();
	}

	class FastIO extends PrintWriter {
		BufferedReader br;
		StringTokenizer st;

		public FastIO(OutputStream psOut) {
			super(psOut);
			try {
				br = new BufferedReader(new FileReader(new File("in")));
			} catch (Exception e) {
				br = new BufferedReader(new InputStreamReader(System.in));
			}
			eat("");
		}

		public void eat(String s) {
			st = new StringTokenizer(s);
		}

		public String nextLine() {
			try {
				return br.readLine();
			} catch (IOException e) {
				throw new IOError(e);
			}
		}

		public boolean hasNext() {
			while (!st.hasMoreTokens()) {
				String s = nextLine();
				if (s == null)
					return false;
				eat(s);
			}
			return true;
		}

		public String next() {
			hasNext();
			return st.nextToken();
		}

		public int nextInt() {
			return Integer.parseInt(next());
		}

		public long nextLong() {
			return Long.parseLong(next());
		}

		public double nextDouble() {
			return Double.parseDouble(next());
		}

		public void println(Object... o) {
			for (Object i : o)
				print(i + " ");
			println();
		}
	}

}


import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;

public class D {

	public static void main(String[] args) {

		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();

		long[] vol = new long[n];

		for(int i = 0; i < n; i++) {
			int r = scanner.nextInt();
			int h = scanner.nextInt();
			vol[i] = (int) (Math.pow(r, 2) * h);

		}

		long[] sort = Arrays.copyOf(vol, n);
		Arrays.sort(sort);
		Map<Long, Integer> map = new HashMap<Long, Integer>();
		int count = 0;
		for(long e : sort) {
			if(!map.containsKey(e)) {
				map.put(e, count++);
			}

		}

//		for(Entry<Integer, Integer> e : map.entrySet()) {
//			System.out.println("K: " + e.getKey() + " V: " + e.getValue());
//
//		}

		long[] zeroes = new long[n];
		SegmentTree tree = new SegmentTree(zeroes);

		for(int i = 0; i < n; i++) {
			long prev = (map.get(vol[i]) == 0) ? 0 : tree.RMaxQ(0, map.get(vol[i]) - 1);
			tree.update(map.get(vol[i]) , map.get(vol[i]) , prev + vol[i]);

		}

		System.out.println(Math.PI * tree.RMaxQ(0, n - 1));

	}

	public static class SegmentTree {

		private Node[] heap;
		private long[] array;
		private int size;

		/**
		 * Time-Complexity:  O(n*log(n))
		 *
		 * @param array the Initialization array
		 */
		public SegmentTree(long[] array) {
			this.array = Arrays.copyOf(array, array.length);
			//The max size of this array is about 2 * 2 ^ log2(n) + 1
			size = (int) (2 * Math.pow(2.0, Math.floor((Math.log((double) array.length) / Math.log(2.0)) + 1)));
			heap = new Node[size];
			build(1, 0, array.length);
		}

		public int size() {
			return array.length;
		}

		//Initialize the Nodes of the Segment tree
		private void build(int v, int from, int size) {
			heap[v] = new Node();
			heap[v].from = from;
			heap[v].to = from + size - 1;

			if (size == 1) {
				heap[v].sum = array[from];
				heap[v].min = array[from];
				heap[v].max = array[from];
			} else {
				//Build childs
				build(2 * v, from, size / 2);
				build(2 * v + 1, from + size / 2, size - size / 2);

				heap[v].sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				//min = min of the children
				heap[v].min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				heap[v].max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		/**
		 * Range Sum Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RSQ(int from, int to) {
			return RSQ(1, from, to);
		}

		private long RSQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Sum without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return (to - from + 1) * n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].sum;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftSum = RSQ(2 * v, from, to);
				long rightSum = RSQ(2 * v + 1, from, to);

				return leftSum + rightSum;
			}

			return 0;
		}

		/**
		 * Range Min Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RMinQ(int from, int to) {
			return RMinQ(1, from, to);
		}

		private long RMinQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].min;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftMin = RMinQ(2 * v, from, to);
				long rightMin = RMinQ(2 * v + 1, from, to);

				return Math.min(leftMin, rightMin);
			}

			return Integer.MAX_VALUE;
		}

		/**
		 * Range Max Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RMaxQ(int from, int to) {
			return RMaxQ(1, from, to);
		}

		private long RMaxQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].max;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftMax = RMaxQ(2 * v, from, to);
				long rightMax = RMaxQ(2 * v + 1, from, to);

				return Math.max(leftMax, rightMax);
			}

			return 0;
		}

		/**
		 * Range Update Operation.
		 * With this operation you can update either one position or a range of positions with a given number.
		 * The update operations will update the less it can to update the whole range (Lazy Propagation).
		 * The values will be propagated lazily from top to bottom of the segment tree.
		 * This behavior is really useful for updates on portions of the array
		 * <p/>
		 * Time-Complexity: O(log(n))
		 *
		 * @param from
		 * @param to
		 * @param value
		 */
		public void update(int from, int to, long value) {
			update(1, from, to, value);
		}

		private void update(int v, int from, int to, long value) {

			//The Node of the heap tree represents a range of the array with bounds: [n.from, n.to]
			Node n = heap[v];

			/**
			 * If the updating-range contains the portion of the current Node  We lazily update it.
			 * This means We do NOT update each position of the vector, but update only some temporal
			 * values into the Node; such values into the Node will be propagated down to its children only when they need to.
			 */
			if (contains(from, to, n.from, n.to)) {
				change(n, value);
			}

			if (n.size() == 1) return;

			if (intersects(from, to, n.from, n.to)) {
				/**
				 * Before keeping going down to the tree We need to propagate the
				 * the values that have been temporally/lazily saved into this Node to its children
				 * So that when We visit them the values  are properly updated
				 */
				propagate(v);

				update(2 * v, from, to, value);
				update(2 * v + 1, from, to, value);

				n.sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				n.min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				n.max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		//Propagate temporal values to children
		private void propagate(int v) {
			Node n = heap[v];

			if (n.pendingVal != null) {
				change(heap[2 * v], n.pendingVal);
				change(heap[2 * v + 1], n.pendingVal);
				n.pendingVal = null; //unset the pending propagation value
			}
		}

		//Save the temporal values that will be propagated lazily
		private void change(Node n, long value) {
			n.pendingVal = value;
			n.sum = n.size() * value;
			n.min = value;
			n.max = value;
			array[n.from] = value;

		}

		//Test if the range1 contains range2
		private boolean contains(int from1, int to1, int from2, int to2) {
			return from2 >= from1 && to2 <= to1;
		}

		//check inclusive intersection, test if range1[from1, to1] intersects range2[from2, to2]
		private boolean intersects(int from1, int to1, int from2, int to2) {
			return from1 <= from2 && to1 >= from2   //  (.[..)..] or (.[...]..)
					|| from1 >= from2 && from1 <= to2; // [.(..]..) or [..(..)..
		}

		//The Node class represents a partition range of the array.
		class Node {
			long sum;
			long min;
			long max;
			//Here We store the value that will be propagated lazily
			Long pendingVal = null;
			int from;
			int to;

			int size() {
				return to - from + 1;
			}

		}

	}

}


import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;

public class D {

	public static void main(String[] args) {

		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();

		long[] vol = new long[n];

		for(int i = 0; i < n; i++) {
			int r = scanner.nextInt();
			int h = scanner.nextInt();
			vol[i] = (long) (Math.pow(r, 2) * h);

		}

		long[] sort = Arrays.copyOf(vol, n);
		Arrays.sort(sort);
		Map<Long, Integer> map = new HashMap<Long, Integer>();
		int count = 0;
		for(long e : sort) {
			if(!map.containsKey(e)) {
				map.put(e, count++);
			}

		}

//		for(Entry<Integer, Integer> e : map.entrySet()) {
//			System.out.println("K: " + e.getKey() + " V: " + e.getValue());
//
//		}

		long[] zeroes = new long[n];
		SegmentTree tree = new SegmentTree(zeroes);

		for(int i = 0; i < n; i++) {
			long prev = (map.get(vol[i]) == 0) ? 0 : tree.RMaxQ(0, map.get(vol[i]) - 1);
			tree.update(map.get(vol[i]) , map.get(vol[i]) , prev + vol[i]);

		}

		System.out.println(Math.PI * tree.RMaxQ(0, n - 1));

	}

	public static class SegmentTree {

		private Node[] heap;
		private long[] array;
		private int size;

		/**
		 * Time-Complexity:  O(n*log(n))
		 *
		 * @param array the Initialization array
		 */
		public SegmentTree(long[] array) {
			this.array = Arrays.copyOf(array, array.length);
			//The max size of this array is about 2 * 2 ^ log2(n) + 1
			size = (int) (2 * Math.pow(2.0, Math.floor((Math.log((double) array.length) / Math.log(2.0)) + 1)));
			heap = new Node[size];
			build(1, 0, array.length);
		}

		public int size() {
			return array.length;
		}

		//Initialize the Nodes of the Segment tree
		private void build(int v, int from, int size) {
			heap[v] = new Node();
			heap[v].from = from;
			heap[v].to = from + size - 1;

			if (size == 1) {
				heap[v].sum = array[from];
				heap[v].min = array[from];
				heap[v].max = array[from];
			} else {
				//Build childs
				build(2 * v, from, size / 2);
				build(2 * v + 1, from + size / 2, size - size / 2);

				heap[v].sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				//min = min of the children
				heap[v].min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				heap[v].max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		/**
		 * Range Sum Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RSQ(int from, int to) {
			return RSQ(1, from, to);
		}

		private long RSQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Sum without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return (to - from + 1) * n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].sum;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftSum = RSQ(2 * v, from, to);
				long rightSum = RSQ(2 * v + 1, from, to);

				return leftSum + rightSum;
			}

			return 0;
		}

		/**
		 * Range Min Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RMinQ(int from, int to) {
			return RMinQ(1, from, to);
		}

		private long RMinQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].min;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftMin = RMinQ(2 * v, from, to);
				long rightMin = RMinQ(2 * v + 1, from, to);

				return Math.min(leftMin, rightMin);
			}

			return Integer.MAX_VALUE;
		}

		/**
		 * Range Max Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RMaxQ(int from, int to) {
			return RMaxQ(1, from, to);
		}

		private long RMaxQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].max;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftMax = RMaxQ(2 * v, from, to);
				long rightMax = RMaxQ(2 * v + 1, from, to);

				return Math.max(leftMax, rightMax);
			}

			return 0;
		}

		/**
		 * Range Update Operation.
		 * With this operation you can update either one position or a range of positions with a given number.
		 * The update operations will update the less it can to update the whole range (Lazy Propagation).
		 * The values will be propagated lazily from top to bottom of the segment tree.
		 * This behavior is really useful for updates on portions of the array
		 * <p/>
		 * Time-Complexity: O(log(n))
		 *
		 * @param from
		 * @param to
		 * @param value
		 */
		public void update(int from, int to, long value) {
			update(1, from, to, value);
		}

		private void update(int v, int from, int to, long value) {

			//The Node of the heap tree represents a range of the array with bounds: [n.from, n.to]
			Node n = heap[v];

			/**
			 * If the updating-range contains the portion of the current Node  We lazily update it.
			 * This means We do NOT update each position of the vector, but update only some temporal
			 * values into the Node; such values into the Node will be propagated down to its children only when they need to.
			 */
			if (contains(from, to, n.from, n.to)) {
				change(n, value);
			}

			if (n.size() == 1) return;

			if (intersects(from, to, n.from, n.to)) {
				/**
				 * Before keeping going down to the tree We need to propagate the
				 * the values that have been temporally/lazily saved into this Node to its children
				 * So that when We visit them the values  are properly updated
				 */
				propagate(v);

				update(2 * v, from, to, value);
				update(2 * v + 1, from, to, value);

				n.sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				n.min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				n.max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		//Propagate temporal values to children
		private void propagate(int v) {
			Node n = heap[v];

			if (n.pendingVal != null) {
				change(heap[2 * v], n.pendingVal);
				change(heap[2 * v + 1], n.pendingVal);
				n.pendingVal = null; //unset the pending propagation value
			}
		}

		//Save the temporal values that will be propagated lazily
		private void change(Node n, long value) {
			n.pendingVal = value;
			n.sum = n.size() * value;
			n.min = value;
			n.max = value;
			array[n.from] = value;

		}

		//Test if the range1 contains range2
		private boolean contains(int from1, int to1, int from2, int to2) {
			return from2 >= from1 && to2 <= to1;
		}

		//check inclusive intersection, test if range1[from1, to1] intersects range2[from2, to2]
		private boolean intersects(int from1, int to1, int from2, int to2) {
			return from1 <= from2 && to1 >= from2   //  (.[..)..] or (.[...]..)
					|| from1 >= from2 && from1 <= to2; // [.(..]..) or [..(..)..
		}

		//The Node class represents a partition range of the array.
		class Node {
			long sum;
			long min;
			long max;
			//Here We store the value that will be propagated lazily
			Long pendingVal = null;
			int from;
			int to;

			int size() {
				return to - from + 1;
			}

		}

	}

}


import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class D {

	public static void main(String[] args) {

		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();

		int[] vol = new int[n];

		for(int i = 0; i < n; i++) {
			int r = scanner.nextInt();
			int h = scanner.nextInt();
			vol[i] = (int) (Math.pow(r, 2) * h);

		}

		int[] sort = Arrays.copyOf(vol, n);
		Arrays.sort(sort);
		Map<Integer, Integer> map = new HashMap<Integer, Integer>();
		int count = 0;
		for(int e : sort) {
			if(!map.containsKey(e)) {
				map.put(e, count++);
			}

		}

		long[] zeroes = new long[n];
		SegmentTree tree = new SegmentTree(zeroes);

		for(int i = 0; i < n; i++) {
			if(map.get(vol[i]) == 0) {
				tree.update(0 , 0 ,  vol[i]);
				continue;

			}
			long prev = tree.RMaxQ(0, map.get(vol[i]) - 1);
			tree.update(map.get(vol[i]) , map.get(vol[i]) , prev + vol[i]);

		}

		System.out.println(1l * Math.PI * tree.RMaxQ(0, n));

	}

	public static class SegmentTree {

		private Node[] heap;
		private long[] array;
		private int size;

		/**
		 * Time-Complexity:  O(n*log(n))
		 *
		 * @param array the Initialization array
		 */
		public SegmentTree(long[] array) {
			this.array = Arrays.copyOf(array, array.length);
			//The max size of this array is about 2 * 2 ^ log2(n) + 1
			size = (int) (2 * Math.pow(2.0, Math.floor((Math.log((double) array.length) / Math.log(2.0)) + 1)));
			heap = new Node[size];
			build(1, 0, array.length);
		}

		public int size() {
			return array.length;
		}

		//Initialize the Nodes of the Segment tree
		private void build(int v, int from, int size) {
			heap[v] = new Node();
			heap[v].from = from;
			heap[v].to = from + size - 1;

			if (size == 1) {
				heap[v].sum = array[from];
				heap[v].min = array[from];
				heap[v].max = array[from];
			} else {
				//Build childs
				build(2 * v, from, size / 2);
				build(2 * v + 1, from + size / 2, size - size / 2);

				heap[v].sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				//min = min of the children
				heap[v].min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				heap[v].max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		/**
		 * Range Sum Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RSQ(int from, int to) {
			return RSQ(1, from, to);
		}

		private long RSQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Sum without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return (to - from + 1) * n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].sum;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftSum = RSQ(2 * v, from, to);
				long rightSum = RSQ(2 * v + 1, from, to);

				return leftSum + rightSum;
			}

			return 0;
		}

		/**
		 * Range Min Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RMinQ(int from, int to) {
			return RMinQ(1, from, to);
		}

		private long RMinQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].min;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftMin = RMinQ(2 * v, from, to);
				long rightMin = RMinQ(2 * v + 1, from, to);

				return Math.min(leftMin, rightMin);
			}

			return Integer.MAX_VALUE;
		}

		/**
		 * Range Max Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RMaxQ(int from, int to) {
			return RMaxQ(1, from, to);
		}

		private long RMaxQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].max;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftMax = RMaxQ(2 * v, from, to);
				long rightMax = RMaxQ(2 * v + 1, from, to);

				return Math.max(leftMax, rightMax);
			}

			return 0;
		}

		/**
		 * Range Update Operation.
		 * With this operation you can update either one position or a range of positions with a given number.
		 * The update operations will update the less it can to update the whole range (Lazy Propagation).
		 * The values will be propagated lazily from top to bottom of the segment tree.
		 * This behavior is really useful for updates on portions of the array
		 * <p/>
		 * Time-Complexity: O(log(n))
		 *
		 * @param from
		 * @param to
		 * @param value
		 */
		public void update(int from, int to, long value) {
			update(1, from, to, value);
		}

		private void update(int v, int from, int to, long value) {

			//The Node of the heap tree represents a range of the array with bounds: [n.from, n.to]
			Node n = heap[v];

			/**
			 * If the updating-range contains the portion of the current Node  We lazily update it.
			 * This means We do NOT update each position of the vector, but update only some temporal
			 * values into the Node; such values into the Node will be propagated down to its children only when they need to.
			 */
			if (contains(from, to, n.from, n.to)) {
				change(n, value);
			}

			if (n.size() == 1) return;

			if (intersects(from, to, n.from, n.to)) {
				/**
				 * Before keeping going down to the tree We need to propagate the
				 * the values that have been temporally/lazily saved into this Node to its children
				 * So that when We visit them the values  are properly updated
				 */
				propagate(v);

				update(2 * v, from, to, value);
				update(2 * v + 1, from, to, value);

				n.sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				n.min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				n.max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		//Propagate temporal values to children
		private void propagate(int v) {
			Node n = heap[v];

			if (n.pendingVal != null) {
				change(heap[2 * v], n.pendingVal);
				change(heap[2 * v + 1], n.pendingVal);
				n.pendingVal = null; //unset the pending propagation value
			}
		}

		//Save the temporal values that will be propagated lazily
		private void change(Node n, long value) {
			n.pendingVal = value;
			n.sum = n.size() * value;
			n.min = value;
			n.max = value;
			array[n.from] = value;

		}

		//Test if the range1 contains range2
		private boolean contains(int from1, int to1, int from2, int to2) {
			return from2 >= from1 && to2 <= to1;
		}

		//check inclusive intersection, test if range1[from1, to1] intersects range2[from2, to2]
		private boolean intersects(int from1, int to1, int from2, int to2) {
			return from1 <= from2 && to1 >= from2   //  (.[..)..] or (.[...]..)
					|| from1 >= from2 && from1 <= to2; // [.(..]..) or [..(..)..
		}

		//The Node class represents a partition range of the array.
		class Node {
			long sum;
			long min;
			long max;
			//Here We store the value that will be propagated lazily
			Long pendingVal = null;
			int from;
			int to;

			int size() {
				return to - from + 1;
			}

		}

	}

}


import java.util.Arrays;
import java.util.Scanner;

public class D {

	public static void main(String[] args) {

		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();

		double[] vol = new double[n];

		for(int i = 0; i < n; i++) {
			int r = scanner.nextInt();
			int h = scanner.nextInt();
			vol[i] = (Math.PI * Math.pow(r, 2) * h);

		}

		double[] dp = new double[n];

		for(int i = 0; i < n; i++) {
			dp[i] = vol[i];

		}

		for(int i = 1; i < n; i++) {
			for(int j = 0; j <= i; j++) {

				if(vol[i] > vol[j]) {
					dp[i] = Math.max(dp[i], dp[j] + vol[i]);

				}

			}

		}

		double max = 0;
		for(double v : dp) {
			max = Math.max(v, max);

		}

		System.out.println(max);

	}

}


import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class D {

	public static void main(String[] args) {

		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();

		int[] vol = new int[n];

		for(int i = 0; i < n; i++) {
			int r = scanner.nextInt();
			int h = scanner.nextInt();
			vol[i] = (int) (Math.pow(r, 2) * h);

		}

		int[] sort = Arrays.copyOf(vol, n);
		Arrays.sort(sort);
		Map<Integer, Integer> map = new HashMap<Integer, Integer>();
		int count = 0;
		for(int e : sort) {
			if(!map.containsKey(e)) {
				map.put(e, count++);
			}

		}

		int[] zeroes = new int[n];
		SegmentTree tree = new SegmentTree(zeroes);

		for(int i = 0; i < n; i++) {
			if(map.get(vol[i]) == 0) {
				tree.update(0 , 0 ,  vol[i]);
				continue;

			}
			int prev = tree.RMaxQ(0, map.get(vol[i]) - 1);
			tree.update(map.get(vol[i]) , map.get(vol[i]) , prev + vol[i]);

		}

		System.out.println(Math.PI * tree.RMaxQ(0, n));

	}

	public static class SegmentTree {

		private Node[] heap;
		private int[] array;
		private int size;

		/**
		 * Time-Complexity:  O(n*log(n))
		 *
		 * @param array the Initialization array
		 */
		public SegmentTree(int[] array) {
			this.array = Arrays.copyOf(array, array.length);
			//The max size of this array is about 2 * 2 ^ log2(n) + 1
			size = (int) (2 * Math.pow(2.0, Math.floor((Math.log((double) array.length) / Math.log(2.0)) + 1)));
			heap = new Node[size];
			build(1, 0, array.length);
		}

		public int size() {
			return array.length;
		}

		//Initialize the Nodes of the Segment tree
		private void build(int v, int from, int size) {
			heap[v] = new Node();
			heap[v].from = from;
			heap[v].to = from + size - 1;

			if (size == 1) {
				heap[v].sum = array[from];
				heap[v].min = array[from];
				heap[v].max = array[from];
			} else {
				//Build childs
				build(2 * v, from, size / 2);
				build(2 * v + 1, from + size / 2, size - size / 2);

				heap[v].sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				//min = min of the children
				heap[v].min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				heap[v].max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		/**
		 * Range Sum Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public int RSQ(int from, int to) {
			return RSQ(1, from, to);
		}

		private int RSQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Sum without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return (to - from + 1) * n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].sum;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				int leftSum = RSQ(2 * v, from, to);
				int rightSum = RSQ(2 * v + 1, from, to);

				return leftSum + rightSum;
			}

			return 0;
		}

		/**
		 * Range Min Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public int RMinQ(int from, int to) {
			return RMinQ(1, from, to);
		}

		private int RMinQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].min;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				int leftMin = RMinQ(2 * v, from, to);
				int rightMin = RMinQ(2 * v + 1, from, to);

				return Math.min(leftMin, rightMin);
			}

			return Integer.MAX_VALUE;
		}

		/**
		 * Range Max Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public int RMaxQ(int from, int to) {
			return RMaxQ(1, from, to);
		}

		private int RMaxQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].max;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				int leftMax = RMaxQ(2 * v, from, to);
				int rightMax = RMaxQ(2 * v + 1, from, to);

				return Math.max(leftMax, rightMax);
			}

			return 0;
		}

		/**
		 * Range Update Operation.
		 * With this operation you can update either one position or a range of positions with a given number.
		 * The update operations will update the less it can to update the whole range (Lazy Propagation).
		 * The values will be propagated lazily from top to bottom of the segment tree.
		 * This behavior is really useful for updates on portions of the array
		 * <p/>
		 * Time-Complexity: O(log(n))
		 *
		 * @param from
		 * @param to
		 * @param value
		 */
		public void update(int from, int to, int value) {
			update(1, from, to, value);
		}

		private void update(int v, int from, int to, int value) {

			//The Node of the heap tree represents a range of the array with bounds: [n.from, n.to]
			Node n = heap[v];

			/**
			 * If the updating-range contains the portion of the current Node  We lazily update it.
			 * This means We do NOT update each position of the vector, but update only some temporal
			 * values into the Node; such values into the Node will be propagated down to its children only when they need to.
			 */
			if (contains(from, to, n.from, n.to)) {
				change(n, value);
			}

			if (n.size() == 1) return;

			if (intersects(from, to, n.from, n.to)) {
				/**
				 * Before keeping going down to the tree We need to propagate the
				 * the values that have been temporally/lazily saved into this Node to its children
				 * So that when We visit them the values  are properly updated
				 */
				propagate(v);

				update(2 * v, from, to, value);
				update(2 * v + 1, from, to, value);

				n.sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				n.min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				n.max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		//Propagate temporal values to children
		private void propagate(int v) {
			Node n = heap[v];

			if (n.pendingVal != null) {
				change(heap[2 * v], n.pendingVal);
				change(heap[2 * v + 1], n.pendingVal);
				n.pendingVal = null; //unset the pending propagation value
			}
		}

		//Save the temporal values that will be propagated lazily
		private void change(Node n, int value) {
			n.pendingVal = value;
			n.sum = n.size() * value;
			n.min = value;
			n.max = value;
			array[n.from] = value;

		}

		//Test if the range1 contains range2
		private boolean contains(int from1, int to1, int from2, int to2) {
			return from2 >= from1 && to2 <= to1;
		}

		//check inclusive intersection, test if range1[from1, to1] intersects range2[from2, to2]
		private boolean intersects(int from1, int to1, int from2, int to2) {
			return from1 <= from2 && to1 >= from2   //  (.[..)..] or (.[...]..)
					|| from1 >= from2 && from1 <= to2; // [.(..]..) or [..(..)..
		}

		//The Node class represents a partition range of the array.
		class Node {
			int sum;
			int min;
			int max;
			//Here We store the value that will be propagated lazily
			Integer pendingVal = null;
			int from;
			int to;

			int size() {
				return to - from + 1;
			}

		}

	}

}


import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class D {

	public static void main(String[] args) {

		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();

		int[] vol = new int[n];

		for(int i = 0; i < n; i++) {
			int r = scanner.nextInt();
			int h = scanner.nextInt();
			vol[i] = (int) (Math.pow(r, 2) * h);

		}

		int[] sort = Arrays.copyOf(vol, n);
		Arrays.sort(sort);
		Map<Integer, Integer> map = new HashMap<Integer, Integer>();
		int count = 0;
		for(int e : sort) {
			if(!map.containsKey(e)) {
				map.put(e, count++);
			}

		}

		int[] zeroes = new int[n];
		SegmentTree tree = new SegmentTree(zeroes);

		for(int i = 0; i < n; i++) {
			if(map.get(vol[i]) == 0) {
				tree.update(0 , 0 ,  vol[i]);
				continue;

			}
			int prev = tree.RMaxQ(0, map.get(vol[i]) - 1);
			tree.update(map.get(vol[i]) , map.get(vol[i]) , prev + vol[i]);

		}

		System.out.println(1l * Math.PI * tree.RMaxQ(0, n));

	}

	public static class SegmentTree {

		private Node[] heap;
		private int[] array;
		private int size;

		/**
		 * Time-Complexity:  O(n*log(n))
		 *
		 * @param array the Initialization array
		 */
		public SegmentTree(int[] array) {
			this.array = Arrays.copyOf(array, array.length);
			//The max size of this array is about 2 * 2 ^ log2(n) + 1
			size = (int) (2 * Math.pow(2.0, Math.floor((Math.log((double) array.length) / Math.log(2.0)) + 1)));
			heap = new Node[size];
			build(1, 0, array.length);
		}

		public int size() {
			return array.length;
		}

		//Initialize the Nodes of the Segment tree
		private void build(int v, int from, int size) {
			heap[v] = new Node();
			heap[v].from = from;
			heap[v].to = from + size - 1;

			if (size == 1) {
				heap[v].sum = array[from];
				heap[v].min = array[from];
				heap[v].max = array[from];
			} else {
				//Build childs
				build(2 * v, from, size / 2);
				build(2 * v + 1, from + size / 2, size - size / 2);

				heap[v].sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				//min = min of the children
				heap[v].min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				heap[v].max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		/**
		 * Range Sum Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public int RSQ(int from, int to) {
			return RSQ(1, from, to);
		}

		private int RSQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Sum without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return (to - from + 1) * n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].sum;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				int leftSum = RSQ(2 * v, from, to);
				int rightSum = RSQ(2 * v + 1, from, to);

				return leftSum + rightSum;
			}

			return 0;
		}

		/**
		 * Range Min Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public int RMinQ(int from, int to) {
			return RMinQ(1, from, to);
		}

		private int RMinQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].min;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				int leftMin = RMinQ(2 * v, from, to);
				int rightMin = RMinQ(2 * v + 1, from, to);

				return Math.min(leftMin, rightMin);
			}

			return Integer.MAX_VALUE;
		}

		/**
		 * Range Max Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public int RMaxQ(int from, int to) {
			return RMaxQ(1, from, to);
		}

		private int RMaxQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].max;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				int leftMax = RMaxQ(2 * v, from, to);
				int rightMax = RMaxQ(2 * v + 1, from, to);

				return Math.max(leftMax, rightMax);
			}

			return 0;
		}

		/**
		 * Range Update Operation.
		 * With this operation you can update either one position or a range of positions with a given number.
		 * The update operations will update the less it can to update the whole range (Lazy Propagation).
		 * The values will be propagated lazily from top to bottom of the segment tree.
		 * This behavior is really useful for updates on portions of the array
		 * <p/>
		 * Time-Complexity: O(log(n))
		 *
		 * @param from
		 * @param to
		 * @param value
		 */
		public void update(int from, int to, int value) {
			update(1, from, to, value);
		}

		private void update(int v, int from, int to, int value) {

			//The Node of the heap tree represents a range of the array with bounds: [n.from, n.to]
			Node n = heap[v];

			/**
			 * If the updating-range contains the portion of the current Node  We lazily update it.
			 * This means We do NOT update each position of the vector, but update only some temporal
			 * values into the Node; such values into the Node will be propagated down to its children only when they need to.
			 */
			if (contains(from, to, n.from, n.to)) {
				change(n, value);
			}

			if (n.size() == 1) return;

			if (intersects(from, to, n.from, n.to)) {
				/**
				 * Before keeping going down to the tree We need to propagate the
				 * the values that have been temporally/lazily saved into this Node to its children
				 * So that when We visit them the values  are properly updated
				 */
				propagate(v);

				update(2 * v, from, to, value);
				update(2 * v + 1, from, to, value);

				n.sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				n.min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				n.max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		//Propagate temporal values to children
		private void propagate(int v) {
			Node n = heap[v];

			if (n.pendingVal != null) {
				change(heap[2 * v], n.pendingVal);
				change(heap[2 * v + 1], n.pendingVal);
				n.pendingVal = null; //unset the pending propagation value
			}
		}

		//Save the temporal values that will be propagated lazily
		private void change(Node n, int value) {
			n.pendingVal = value;
			n.sum = n.size() * value;
			n.min = value;
			n.max = value;
			array[n.from] = value;

		}

		//Test if the range1 contains range2
		private boolean contains(int from1, int to1, int from2, int to2) {
			return from2 >= from1 && to2 <= to1;
		}

		//check inclusive intersection, test if range1[from1, to1] intersects range2[from2, to2]
		private boolean intersects(int from1, int to1, int from2, int to2) {
			return from1 <= from2 && to1 >= from2   //  (.[..)..] or (.[...]..)
					|| from1 >= from2 && from1 <= to2; // [.(..]..) or [..(..)..
		}

		//The Node class represents a partition range of the array.
		class Node {
			int sum;
			int min;
			int max;
			//Here We store the value that will be propagated lazily
			Integer pendingVal = null;
			int from;
			int to;

			int size() {
				return to - from + 1;
			}

		}

	}

}


import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;

public class D {

	public static void main(String[] args) {

		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();

		int[] vol = new int[n];

		for(int i = 0; i < n; i++) {
			int r = scanner.nextInt();
			int h = scanner.nextInt();
			vol[i] = (int) (Math.pow(r, 2) * h);

		}

		int[] sort = Arrays.copyOf(vol, n);
		Arrays.sort(sort);
		Map<Integer, Integer> map = new HashMap<Integer, Integer>();
		int count = 0;
		for(int e : sort) {
			if(!map.containsKey(e)) {
				map.put(e, count++);
			}

		}

//		for(Entry<Integer, Integer> e : map.entrySet()) {
//			System.out.println("K: " + e.getKey() + " V: " + e.getValue());
//
//		}

		long[] zeroes = new long[n];
		SegmentTree tree = new SegmentTree(zeroes);

		for(int i = 0; i < n; i++) {
			long prev = (map.get(vol[i]) == 0) ? 0 : tree.RMaxQ(0, map.get(vol[i]) - 1);
			tree.update(map.get(vol[i]) , map.get(vol[i]) , prev + vol[i]);

		}

		System.out.println(1l * Math.PI * tree.RMaxQ(0, n + 10));

	}

	public static class SegmentTree {

		private Node[] heap;
		private long[] array;
		private int size;

		/**
		 * Time-Complexity:  O(n*log(n))
		 *
		 * @param array the Initialization array
		 */
		public SegmentTree(long[] array) {
			this.array = Arrays.copyOf(array, array.length);
			//The max size of this array is about 2 * 2 ^ log2(n) + 1
			size = (int) (2 * Math.pow(2.0, Math.floor((Math.log((double) array.length) / Math.log(2.0)) + 1)));
			heap = new Node[size];
			build(1, 0, array.length);
		}

		public int size() {
			return array.length;
		}

		//Initialize the Nodes of the Segment tree
		private void build(int v, int from, int size) {
			heap[v] = new Node();
			heap[v].from = from;
			heap[v].to = from + size - 1;

			if (size == 1) {
				heap[v].sum = array[from];
				heap[v].min = array[from];
				heap[v].max = array[from];
			} else {
				//Build childs
				build(2 * v, from, size / 2);
				build(2 * v + 1, from + size / 2, size - size / 2);

				heap[v].sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				//min = min of the children
				heap[v].min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				heap[v].max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		/**
		 * Range Sum Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RSQ(int from, int to) {
			return RSQ(1, from, to);
		}

		private long RSQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Sum without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return (to - from + 1) * n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].sum;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftSum = RSQ(2 * v, from, to);
				long rightSum = RSQ(2 * v + 1, from, to);

				return leftSum + rightSum;
			}

			return 0;
		}

		/**
		 * Range Min Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RMinQ(int from, int to) {
			return RMinQ(1, from, to);
		}

		private long RMinQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].min;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftMin = RMinQ(2 * v, from, to);
				long rightMin = RMinQ(2 * v + 1, from, to);

				return Math.min(leftMin, rightMin);
			}

			return Integer.MAX_VALUE;
		}

		/**
		 * Range Max Query
		 *
		 * Time-Complexity: O(log(n))
		 */
		public long RMaxQ(int from, int to) {
			return RMaxQ(1, from, to);
		}

		private long RMaxQ(int v, int from, int to) {
			Node n = heap[v];

			//If you did a range update that contained this node, you can infer the Min value without going down the tree
			if (n.pendingVal != null && contains(n.from, n.to, from, to)) {
				return n.pendingVal;
			}

			if (contains(from, to, n.from, n.to)) {
				return heap[v].max;
			}

			if (intersects(from, to, n.from, n.to)) {
				propagate(v);
				long leftMax = RMaxQ(2 * v, from, to);
				long rightMax = RMaxQ(2 * v + 1, from, to);

				return Math.max(leftMax, rightMax);
			}

			return 0;
		}

		/**
		 * Range Update Operation.
		 * With this operation you can update either one position or a range of positions with a given number.
		 * The update operations will update the less it can to update the whole range (Lazy Propagation).
		 * The values will be propagated lazily from top to bottom of the segment tree.
		 * This behavior is really useful for updates on portions of the array
		 * <p/>
		 * Time-Complexity: O(log(n))
		 *
		 * @param from
		 * @param to
		 * @param value
		 */
		public void update(int from, int to, long value) {
			update(1, from, to, value);
		}

		private void update(int v, int from, int to, long value) {

			//The Node of the heap tree represents a range of the array with bounds: [n.from, n.to]
			Node n = heap[v];

			/**
			 * If the updating-range contains the portion of the current Node  We lazily update it.
			 * This means We do NOT update each position of the vector, but update only some temporal
			 * values into the Node; such values into the Node will be propagated down to its children only when they need to.
			 */
			if (contains(from, to, n.from, n.to)) {
				change(n, value);
			}

			if (n.size() == 1) return;

			if (intersects(from, to, n.from, n.to)) {
				/**
				 * Before keeping going down to the tree We need to propagate the
				 * the values that have been temporally/lazily saved into this Node to its children
				 * So that when We visit them the values  are properly updated
				 */
				propagate(v);

				update(2 * v, from, to, value);
				update(2 * v + 1, from, to, value);

				n.sum = heap[2 * v].sum + heap[2 * v + 1].sum;
				n.min = Math.min(heap[2 * v].min, heap[2 * v + 1].min);
				n.max = Math.max(heap[2 * v].max, heap[2 * v + 1].max);

			}
		}

		//Propagate temporal values to children
		private void propagate(int v) {
			Node n = heap[v];

			if (n.pendingVal != null) {
				change(heap[2 * v], n.pendingVal);
				change(heap[2 * v + 1], n.pendingVal);
				n.pendingVal = null; //unset the pending propagation value
			}
		}

		//Save the temporal values that will be propagated lazily
		private void change(Node n, long value) {
			n.pendingVal = value;
			n.sum = n.size() * value;
			n.min = value;
			n.max = value;
			array[n.from] = value;

		}

		//Test if the range1 contains range2
		private boolean contains(int from1, int to1, int from2, int to2) {
			return from2 >= from1 && to2 <= to1;
		}

		//check inclusive intersection, test if range1[from1, to1] intersects range2[from2, to2]
		private boolean intersects(int from1, int to1, int from2, int to2) {
			return from1 <= from2 && to1 >= from2   //  (.[..)..] or (.[...]..)
					|| from1 >= from2 && from1 <= to2; // [.(..]..) or [..(..)..
		}

		//The Node class represents a partition range of the array.
		class Node {
			long sum;
			long min;
			long max;
			//Here We store the value that will be propagated lazily
			Long pendingVal = null;
			int from;
			int to;

			int size() {
				return to - from + 1;
			}

		}

	}

}


import java.awt.*;
import java.io.*;
import java.math.*;
import java.util.*;

public class Main implements Runnable {

    int maxn = (int)1e5+100;
    int mod = (int)1e9+7;
    int inf = (int)1e9;
    int n,m,k;
    Pair p[] = new Pair[maxn];
    long T[] = new long [4*maxn];

    void solve() throws Exception {

    	n = in.nextInt();
    	for (int i=1; i<=n; i++) {
    		long area = getArea(in.nextInt(), in.nextInt());
    		p[i] = new Pair(area, i);
    	}

    	Arrays.sort(p,1,n+1);

    	for (int i=1; i<=n; i++) {
    		long max = getMax(1,1,n,1,p[i].y);
    		update(1, 1, n, p[i].y, max+p[i].x);
    	}

    	double ans = getMax(1,1,n,1,n)*Math.PI;
    	out.println(ans);
    }

    private long getArea(int r, int h) {
    	return r*r*h;
	}

    public long getMax(int v, int tl, int tr, int l, int r) {
    	if (tr<l || r<tl) {
    		return 0;
    	}
    	if (l<=tl && tr<=r) {
    		return T[v];
    	}
    	int mid = (tl+tr)/2;
    	return Math.max(getMax(2*v, tl, mid, l, r) , getMax(2*v+1, mid+1, tr, l, r));
    }

    public void update (int v, int tl, int tr, int pos, long val) {
    	if (tl==tr) {
    		T[v] = val;
    		return;
    	}
    	int mid = (tl+tr)/2;
    	if (pos<=mid) {
    		update (2*v, tl, mid, pos,val);
    	} else {
    		update (2*v+1, mid+1, tr, pos, val);
    	}
    	T[v] = Math.max(T[2*v],T[2*v+1]);
    }

	class Pair implements Comparable<Pair>{
        long x;
        int y;
        public Pair (long x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Pair p) {
            if (x>p.x) return 1;
            else if (x==p.x) {
                if (y<p.y) return 1;
                else if (y==p.y) return 0;
                else return -1;
            } else {
                return -1;
            }

        }
    }

    String fileInName = "network";
    boolean file = false;
    boolean isAcmp = true;

    static Throwable throwable;
    public static void main (String [] args) throws Throwable {
    	Locale.setDefault(new Locale("en", "US"));
        Thread thread = new Thread(null, new Main(), "", (1 << 26));
        thread.start();
        thread.join();
        thread.run();
        if (throwable != null)
            throw throwable;
    }

    FastReader in;
    PrintWriter out;

    public void run() {
        String fileIn = "input.txt";
        String fileOut = "output.txt";

        try {
            if (isAcmp) {
                if (file) {
                    in = new FastReader(new BufferedReader(new FileReader(fileIn)));
                    out = new PrintWriter (fileOut);
                } else {
                    in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                    out = new PrintWriter(System.out);
                }
            } else if (file) {
                in = new FastReader(new BufferedReader(new FileReader(fileInName+".in")));
                out = new PrintWriter (fileInName + ".out");
            } else {
                in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                out = new PrintWriter(System.out);
            }

            solve();
        } catch(Exception e) {
            throwable = e;
        } finally {
            out.close();
        }

    }
}

class FastReader {
    BufferedReader bf;
    StringTokenizer tk = null;

    public FastReader(BufferedReader bf) {
        this.bf = bf;
    }

    public String nextToken () throws Exception {
        if (tk==null || !tk.hasMoreTokens()) {
            tk = new StringTokenizer(bf.readLine());
        }
        if (!tk.hasMoreTokens()) return nextToken();
        else
            return tk.nextToken();
    }

    public int nextInt() throws Exception {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() throws Exception {
        return Long.parseLong(nextToken());
    }

    public double nextDouble() throws Exception {
        return Double.parseDouble(nextToken());
    }
}


import java.awt.*;
import java.io.*;
import java.math.*;
import java.util.*;

public class Main implements Runnable {

    int maxn = (int)1e5+100;
    int mod = (int)1e9+7;
    int inf = (int)1e9;
    int n,m,k;
    Pair p[] = new Pair[maxn];
    double T[] = new double [4*maxn];

    void solve() throws Exception {

    	n = in.nextInt();
    	for (int i=1; i<=n; i++) {
    		double area = getArea(in.nextInt(), in.nextInt());
    		p[i] = new Pair(area, i);
    	}

    	Arrays.sort(p,1,n+1);

    	for (int i=1; i<=n; i++) {
    		double max = getMax(1,1,n,1,p[i].y);
    		update(1, 1, n, p[i].y, max+p[i].x);
    	}

    	double ans = getMax(1,1,n,1,n);
    	out.println(ans);
    }

    private double getArea(int r, int h) {
    	return Math.PI*r*r*h;
	}

    public double getMax(int v, int tl, int tr, int l, int r) {
    	if (tr<l || r<tl) {
    		return 0;
    	}
    	if (l<=tl && tr<=r) {
    		return T[v];
    	}
    	int mid = (tl+tr)/2;
    	return Math.max(getMax(2*v, tl, mid, l, r) , getMax(2*v+1, mid+1, tr, l, r));
    }

    public void update (int v, int tl, int tr, int pos, double val) {
    	if (tl==tr) {
    		T[v] = val;
    		return;
    	}
    	int mid = (tl+tr)/2;
    	if (pos<=mid) {
    		update (2*v, tl, mid, pos,val);
    	} else {
    		update (2*v+1, mid+1, tr, pos, val);
    	}
    	T[v] = Math.max(T[2*v],T[2*v+1]);
    }

	class Pair implements Comparable<Pair>{
        double x;
        int y;
        public Pair (double x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Pair p) {
            if (x>p.x) return 1;
            else if (x==p.x) {
                if (y>p.y) return 1;
                else if (y==p.y) return 0;
                else return -1;
            } else {
                return -1;
            }

        }
    }

    String fileInName = "network";
    boolean file = false;
    boolean isAcmp = true;

    static Throwable throwable;
    public static void main (String [] args) throws Throwable {
    	Locale.setDefault(new Locale("en", "US"));
        Thread thread = new Thread(null, new Main(), "", (1 << 26));
        thread.start();
        thread.join();
        thread.run();
        if (throwable != null)
            throw throwable;
    }

    FastReader in;
    PrintWriter out;

    public void run() {
        String fileIn = "input.txt";
        String fileOut = "output.txt";

        try {
            if (isAcmp) {
                if (file) {
                    in = new FastReader(new BufferedReader(new FileReader(fileIn)));
                    out = new PrintWriter (fileOut);
                } else {
                    in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                    out = new PrintWriter(System.out);
                }
            } else if (file) {
                in = new FastReader(new BufferedReader(new FileReader(fileInName+".in")));
                out = new PrintWriter (fileInName + ".out");
            } else {
                in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                out = new PrintWriter(System.out);
            }

            solve();
        } catch(Exception e) {
            throwable = e;
        } finally {
            out.close();
        }

    }
}

class FastReader {
    BufferedReader bf;
    StringTokenizer tk = null;

    public FastReader(BufferedReader bf) {
        this.bf = bf;
    }

    public String nextToken () throws Exception {
        if (tk==null || !tk.hasMoreTokens()) {
            tk = new StringTokenizer(bf.readLine());
        }
        if (!tk.hasMoreTokens()) return nextToken();
        else
            return tk.nextToken();
    }

    public int nextInt() throws Exception {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() throws Exception {
        return Long.parseLong(nextToken());
    }

    public double nextDouble() throws Exception {
        return Double.parseDouble(nextToken());
    }
}


import java.awt.*;
import java.io.*;
import java.math.*;
import java.util.*;

public class Main implements Runnable {

    int maxn = (int)1e5+100;
    int mod = (int)1e9+7;
    int inf = (int)1e9;
    int n,m,k;
    Pair p[] = new Pair[maxn];
    long T[] = new long [4*maxn];

    void solve() throws Exception {

    	n = in.nextInt();
    	for (int i=1; i<=n; i++) {
    		long area = getArea(in.nextInt(), in.nextInt());
    		p[i] = new Pair(area, i);
    	}

    	Arrays.sort(p,1,n+1);

    	for (int i=1; i<=n; i++) {
    		long max = getMax(1,1,n,1,p[i].y);
    		update(1, 1, n, p[i].y, max+p[i].x);
    	}

    	double ans = (double)getMax(1,1,n,1,n)*1.0;
    	ans*=Math.PI;
    	out.println(ans);
    }

    private long getArea(int r, int h) {
    	return r*r*h;
	}

    public long getMax(int v, int tl, int tr, int l, int r) {
    	if (tr<l || r<tl) {
    		return 0;
    	}
    	if (l<=tl && tr<=r) {
    		return T[v];
    	}
    	int mid = (tl+tr)/2;
    	return Math.max(getMax(2*v, tl, mid, l, r) , getMax(2*v+1, mid+1, tr, l, r));
    }

    public void update (int v, int tl, int tr, int pos, long val) {
    	if (tl==tr) {
    		T[v] = val;
    		return;
    	}
    	int mid = (tl+tr)/2;
    	if (pos<=mid) {
    		update (2*v, tl, mid, pos,val);
    	} else {
    		update (2*v+1, mid+1, tr, pos, val);
    	}
    	T[v] = Math.max(T[2*v],T[2*v+1]);
    }

	class Pair implements Comparable<Pair>{
		long x;
        int y;
        public Pair (long x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Pair p) {
            if (x>p.x) return 1;
            else if (x==p.x) {
                if (y>p.y) return 1;
                else if (y==p.y) return 0;
                else return -1;
            } else {
                return -1;
            }

        }
    }

    String fileInName = "network";
    boolean file = false;
    boolean isAcmp = true;

    static Throwable throwable;
    public static void main (String [] args) throws Throwable {
    	Locale.setDefault(new Locale("en", "US"));
        Thread thread = new Thread(null, new Main(), "", (1 << 26));
        thread.start();
        thread.join();
        thread.run();
        if (throwable != null)
            throw throwable;
    }

    FastReader in;
    PrintWriter out;

    public void run() {
        String fileIn = "input.txt";
        String fileOut = "output.txt";

        try {
            if (isAcmp) {
                if (file) {
                    in = new FastReader(new BufferedReader(new FileReader(fileIn)));
                    out = new PrintWriter (fileOut);
                } else {
                    in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                    out = new PrintWriter(System.out);
                }
            } else if (file) {
                in = new FastReader(new BufferedReader(new FileReader(fileInName+".in")));
                out = new PrintWriter (fileInName + ".out");
            } else {
                in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                out = new PrintWriter(System.out);
            }

            solve();
        } catch(Exception e) {
            throwable = e;
        } finally {
            out.close();
        }

    }
}

class FastReader {
    BufferedReader bf;
    StringTokenizer tk = null;

    public FastReader(BufferedReader bf) {
        this.bf = bf;
    }

    public String nextToken () throws Exception {
        if (tk==null || !tk.hasMoreTokens()) {
            tk = new StringTokenizer(bf.readLine());
        }
        if (!tk.hasMoreTokens()) return nextToken();
        else
            return tk.nextToken();
    }

    public int nextInt() throws Exception {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() throws Exception {
        return Long.parseLong(nextToken());
    }

    public double nextDouble() throws Exception {
        return Double.parseDouble(nextToken());
    }
}


import java.awt.*;
import java.io.*;
import java.math.*;
import java.util.*;

public class Main implements Runnable {

    int maxn = (int)1e5+100;
    int mod = (int)1e9+7;
    int inf = (int)1e9;
    int n,m,k;
    Pair p[] = new Pair[maxn];
    long T[] = new long [4*maxn];

    void solve() throws Exception {

    	n = in.nextInt();
    	for (int i=1; i<=n; i++) {
    		long area = getArea(in.nextInt(), in.nextInt());
    		p[i] = new Pair(area, -i);
    	}

    	Arrays.sort(p,1,n+1);

    	for (int i=1; i<=n; i++) {
    		long max = getMax(1,1,n,1,p[i].y);
    		update(1, 1, n, p[i].y, max+p[i].x);
    	}

    	double ans = getMax(1,1,n,1,n);
    	ans*=Math.PI;
    	out.println(ans);
    }

    private long getArea(int r, int h) {
    	return r*r*h;
	}

    public long getMax(int v, int tl, int tr, int l, int r) {
    	if (tr<l || r<tl) {
    		return 0;
    	}
    	if (l<=tl && tr<=r) {
    		return T[v];
    	}
    	int mid = (tl+tr)/2;
    	return Math.max(getMax(2*v, tl, mid, l, r) , getMax(2*v+1, mid+1, tr, l, r));
    }

    public void update (int v, int tl, int tr, int pos, long val) {
    	if (tl==tr) {
    		T[v] = val;
    		return;
    	}
    	int mid = (tl+tr)/2;
    	if (pos<=mid) {
    		update (2*v, tl, mid, pos,val);
    	} else {
    		update (2*v+1, mid+1, tr, pos, val);
    	}
    	T[v] = Math.max(T[2*v],T[2*v+1]);
    }

	class Pair implements Comparable<Pair>{
        long x;
        int y;
        public Pair (long x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Pair p) {
            if (x>p.x) return 1;
            else if (x==p.x) {
                if (y>p.y) return 1;
                else if (y==p.y) return 0;
                else return -1;
            } else {
                return -1;
            }

        }
    }

    String fileInName = "network";
    boolean file = false;
    boolean isAcmp = true;

    static Throwable throwable;
    public static void main (String [] args) throws Throwable {
    	Locale.setDefault(new Locale("en", "US"));
        Thread thread = new Thread(null, new Main(), "", (1 << 26));
        thread.start();
        thread.join();
        thread.run();
        if (throwable != null)
            throw throwable;
    }

    FastReader in;
    PrintWriter out;

    public void run() {
        String fileIn = "input.txt";
        String fileOut = "output.txt";

        try {
            if (isAcmp) {
                if (file) {
                    in = new FastReader(new BufferedReader(new FileReader(fileIn)));
                    out = new PrintWriter (fileOut);
                } else {
                    in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                    out = new PrintWriter(System.out);
                }
            } else if (file) {
                in = new FastReader(new BufferedReader(new FileReader(fileInName+".in")));
                out = new PrintWriter (fileInName + ".out");
            } else {
                in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                out = new PrintWriter(System.out);
            }

            solve();
        } catch(Exception e) {
            throwable = e;
        } finally {
            out.close();
        }

    }
}

class FastReader {
    BufferedReader bf;
    StringTokenizer tk = null;

    public FastReader(BufferedReader bf) {
        this.bf = bf;
    }

    public String nextToken () throws Exception {
        if (tk==null || !tk.hasMoreTokens()) {
            tk = new StringTokenizer(bf.readLine());
        }
        if (!tk.hasMoreTokens()) return nextToken();
        else
            return tk.nextToken();
    }

    public int nextInt() throws Exception {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() throws Exception {
        return Long.parseLong(nextToken());
    }

    public double nextDouble() throws Exception {
        return Double.parseDouble(nextToken());
    }
}


import java.awt.*;
import java.io.*;
import java.math.*;
import java.util.*;

public class Main implements Runnable {

    int maxn = (int)1e5+100;
    int mod = (int)1e9+7;
    int inf = (int)1e9;
    int n,m,k;
    Pair p[] = new Pair[maxn];
    long T[] = new long [4*maxn];

    void solve() throws Exception {

    	n = in.nextInt();
    	for (int i=1; i<=n; i++) {
    		long area = getArea(in.nextInt(), in.nextInt());
    		p[i] = new Pair(area, i);
    	}

    	Arrays.sort(p,1,n+1);

    	for (int i=1; i<=n; i++) {
    		long max = getMax(1,1,n,1,p[i].y);
    		update(1, 1, n, p[i].y, max+p[i].x);
    	}

    	double ans = getMax(1,1,n,1,n);
    	out.println(ans*Math.PI);
    }

    private long getArea(int r, int h) {
    	return (long)r*r*(long)h;
	}

    public long getMax(int v, int tl, int tr, int l, int r) {
    	if (tr<l || r<tl) {
    		return 0;
    	}
    	if (l<=tl && tr<=r) {
    		return T[v];
    	}
    	int mid = (tl+tr)/2;
    	return Math.max(getMax(2*v, tl, mid, l, r) , getMax(2*v+1, mid+1, tr, l, r));
    }

    public void update (int v, int tl, int tr, int pos, long val) {
    	if (tl==tr) {
    		T[v] = val;
    		return;
    	}
    	int mid = (tl+tr)/2;
    	if (pos<=mid) {
    		update (2*v, tl, mid, pos,val);
    	} else {
    		update (2*v+1, mid+1, tr, pos, val);
    	}
    	T[v] = Math.max(T[2*v],T[2*v+1]);
    }

	class Pair implements Comparable<Pair>{
        long x;
        int y;
        public Pair (long x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Pair p) {
            if (x>p.x) return 1;
            else if (x==p.x) {
                if (y<p.y) return 1;
                else if (y==p.y) return 0;
                else return -1;
            } else {
                return -1;
            }

        }
    }

    String fileInName = "network";
    boolean file = false;
    boolean isAcmp = true;

    static Throwable throwable;
    public static void main (String [] args) throws Throwable {
    	Locale.setDefault(new Locale("en", "US"));
        Thread thread = new Thread(null, new Main(), "", (1 << 26));
        thread.start();
        thread.join();
        thread.run();
        if (throwable != null)
            throw throwable;
    }

    FastReader in;
    PrintWriter out;

    public void run() {
        String fileIn = "input.txt";
        String fileOut = "output.txt";

        try {
            if (isAcmp) {
                if (file) {
                    in = new FastReader(new BufferedReader(new FileReader(fileIn)));
                    out = new PrintWriter (fileOut);
                } else {
                    in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                    out = new PrintWriter(System.out);
                }
            } else if (file) {
                in = new FastReader(new BufferedReader(new FileReader(fileInName+".in")));
                out = new PrintWriter (fileInName + ".out");
            } else {
                in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                out = new PrintWriter(System.out);
            }

            solve();
        } catch(Exception e) {
            throwable = e;
        } finally {
            out.close();
        }

    }
}

class FastReader {
    BufferedReader bf;
    StringTokenizer tk = null;

    public FastReader(BufferedReader bf) {
        this.bf = bf;
    }

    public String nextToken () throws Exception {
        if (tk==null || !tk.hasMoreTokens()) {
            tk = new StringTokenizer(bf.readLine());
        }
        if (!tk.hasMoreTokens()) return nextToken();
        else
            return tk.nextToken();
    }

    public int nextInt() throws Exception {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() throws Exception {
        return Long.parseLong(nextToken());
    }

    public double nextDouble() throws Exception {
        return Double.parseDouble(nextToken());
    }
}


import java.awt.*;
import java.io.*;
import java.math.*;
import java.util.*;

public class Main implements Runnable {

    int maxn = (int)1e5+100;
    int mod = (int)1e9+7;
    int inf = (int)1e9;
    int n,m,k;
    Pair p[] = new Pair[maxn];
    double T[] = new double [4*maxn];

    void solve() throws Exception {

    	n = in.nextInt();
    	for (int i=1; i<=n; i++) {
    		double area = getArea(in.nextInt(), in.nextInt());
    		p[i] = new Pair(area, i);
    	}

    	Arrays.sort(p,1,n+1);

    	for (int i=1; i<=n; i++) {
    		double max = getMax(1,1,n,1,p[i].y);
    		update(1, 1, n, p[i].y, max+p[i].x);
    	}

    	double ans = (double)getMax(1,1,n,1,n)*1.0;
    	ans*=Math.PI;
    	out.println(ans);
    }

    private double getArea(int r, int h) {
    	return r*r*h;
	}

    public double getMax(int v, int tl, int tr, int l, int r) {
    	if (tr<l || r<tl) {
    		return 0;
    	}
    	if (l<=tl && tr<=r) {
    		return T[v];
    	}
    	int mid = (tl+tr)/2;
    	return Math.max(getMax(2*v, tl, mid, l, r) , getMax(2*v+1, mid+1, tr, l, r));
    }

    public void update (int v, int tl, int tr, int pos, double val) {
    	if (tl==tr) {
    		T[v] = val;
    		return;
    	}
    	int mid = (tl+tr)/2;
    	if (pos<=mid) {
    		update (2*v, tl, mid, pos,val);
    	} else {
    		update (2*v+1, mid+1, tr, pos, val);
    	}
    	T[v] = Math.max(T[2*v],T[2*v+1]);
    }

	class Pair implements Comparable<Pair>{
		double x;
        int y;
        public Pair (double x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Pair p) {
            if (x>p.x) return 1;
            else if (x==p.x) {
                if (y>p.y) return 1;
                else if (y==p.y) return 0;
                else return -1;
            } else {
                return -1;
            }

        }
    }

    String fileInName = "network";
    boolean file = false;
    boolean isAcmp = true;

    static Throwable throwable;
    public static void main (String [] args) throws Throwable {
    	Locale.setDefault(new Locale("en", "US"));
        Thread thread = new Thread(null, new Main(), "", (1 << 26));
        thread.start();
        thread.join();
        thread.run();
        if (throwable != null)
            throw throwable;
    }

    FastReader in;
    PrintWriter out;

    public void run() {
        String fileIn = "input.txt";
        String fileOut = "output.txt";

        try {
            if (isAcmp) {
                if (file) {
                    in = new FastReader(new BufferedReader(new FileReader(fileIn)));
                    out = new PrintWriter (fileOut);
                } else {
                    in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                    out = new PrintWriter(System.out);
                }
            } else if (file) {
                in = new FastReader(new BufferedReader(new FileReader(fileInName+".in")));
                out = new PrintWriter (fileInName + ".out");
            } else {
                in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                out = new PrintWriter(System.out);
            }

            solve();
        } catch(Exception e) {
            throwable = e;
        } finally {
            out.close();
        }

    }
}

class FastReader {
    BufferedReader bf;
    StringTokenizer tk = null;

    public FastReader(BufferedReader bf) {
        this.bf = bf;
    }

    public String nextToken () throws Exception {
        if (tk==null || !tk.hasMoreTokens()) {
            tk = new StringTokenizer(bf.readLine());
        }
        if (!tk.hasMoreTokens()) return nextToken();
        else
            return tk.nextToken();
    }

    public int nextInt() throws Exception {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() throws Exception {
        return Long.parseLong(nextToken());
    }

    public double nextDouble() throws Exception {
        return Double.parseDouble(nextToken());
    }
}


import java.awt.*;
import java.io.*;
import java.math.*;
import java.util.*;

public class Main implements Runnable {

    int maxn = (int)1e5+100;
    int mod = (int)1e9+7;
    int inf = (int)1e9;
    int n,m,k;
    Pair p[] = new Pair[maxn];
    double T[] = new double [4*maxn];

    void solve() throws Exception {

    	n = in.nextInt();
    	for (int i=1; i<=n; i++) {
    		double area = getArea(in.nextInt(), in.nextInt());
    		p[i] = new Pair(area, i);
    	}

    	Arrays.sort(p,1,n+1);

    	for (int i=1; i<=n; i++) {
    		double max = getMax(1,1,n,1,p[i].y);
    		update(1, 1, n, p[i].y, max+p[i].x);
    	}

    	double ans = getMax(1,1,n,1,n);
    	out.println(ans);
    }

    private double getArea(int r, int h) {
    	return Math.PI*r*r*h;
	}

    public double getMax(int v, int tl, int tr, int l, int r) {
    	if (tr<l || r<tl) {
    		return 0;
    	}
    	if (l<=tl && tr<=r) {
    		return T[v];
    	}
    	int mid = (tl+tr)/2;
    	return Math.max(getMax(2*v, tl, mid, l, r) , getMax(2*v+1, mid+1, tr, l, r));
    }

    public void update (int v, int tl, int tr, int pos, double val) {
    	if (tl==tr) {
    		T[v] = val;
    		return;
    	}
    	int mid = (tl+tr)/2;
    	if (pos<=mid) {
    		update (2*v, tl, mid, pos,val);
    	} else {
    		update (2*v+1, mid+1, tr, pos, val);
    	}
    	T[v] = Math.max(T[2*v],T[2*v+1]);
    }

	class Pair implements Comparable<Pair>{
        double x;
        int y;
        public Pair (double x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Pair p) {
            if (x>p.x) return 1;
            else if (x==p.x) {
                if (y<p.y) return 1;
                else if (y==p.y) return 0;
                else return -1;
            } else {
                return -1;
            }

        }
    }

    String fileInName = "network";
    boolean file = false;
    boolean isAcmp = true;

    static Throwable throwable;
    public static void main (String [] args) throws Throwable {
    	Locale.setDefault(new Locale("en", "US"));
        Thread thread = new Thread(null, new Main(), "", (1 << 26));
        thread.start();
        thread.join();
        thread.run();
        if (throwable != null)
            throw throwable;
    }

    FastReader in;
    PrintWriter out;

    public void run() {
        String fileIn = "input.txt";
        String fileOut = "output.txt";

        try {
            if (isAcmp) {
                if (file) {
                    in = new FastReader(new BufferedReader(new FileReader(fileIn)));
                    out = new PrintWriter (fileOut);
                } else {
                    in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                    out = new PrintWriter(System.out);
                }
            } else if (file) {
                in = new FastReader(new BufferedReader(new FileReader(fileInName+".in")));
                out = new PrintWriter (fileInName + ".out");
            } else {
                in = new FastReader(new BufferedReader(new InputStreamReader(System.in)));
                out = new PrintWriter(System.out);
            }

            solve();
        } catch(Exception e) {
            throwable = e;
        } finally {
            out.close();
        }

    }
}

class FastReader {
    BufferedReader bf;
    StringTokenizer tk = null;

    public FastReader(BufferedReader bf) {
        this.bf = bf;
    }

    public String nextToken () throws Exception {
        if (tk==null || !tk.hasMoreTokens()) {
            tk = new StringTokenizer(bf.readLine());
        }
        if (!tk.hasMoreTokens()) return nextToken();
        else
            return tk.nextToken();
    }

    public int nextInt() throws Exception {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() throws Exception {
        return Long.parseLong(nextToken());
    }

    public double nextDouble() throws Exception {
        return Double.parseDouble(nextToken());
    }
}


import java.io.*;
import java.util.*;

public class Solution {

    static class MaxSegmentTree {
        long[] base;
        long[] tree;

        MaxSegmentTree(long[] base) {
            this.base = base;
            this.tree = new long[4 * base.length];
        }

        void build(int root, int left, int right) {
            if (left == right)
                tree[root] = base[left];
            else {
                int middle = (left + right) >> 1;
                build(2 * root + 1, left, middle);
                build(2 * root + 2, middle + 1, right);
                tree[root] = Math.max(tree[2 * root + 1], tree[2 * root + 2]);
            }
        }

        long max(int root, int left, int right, int from, int to) {
            if (from > to)
                return 0;

            if (from == left && to == right)
                return tree[root];

            int mid = (left + right) >> 1;
            return Math.max(max(2 * root + 1, left, mid, from, Math.min(to, mid)),
                    max(2 * root + 2, mid + 1, right, Math.max(from, mid + 1), to));
        }

        void update(int root, int left, int right, int index, long value) {
            if (left == right)
                tree[root] = value;
            else {
                int mid = (left + right) >> 1;

                if (index <= mid)
                    update(2 * root + 1, left, mid, index, value);
                else
                    update(2 * root + 2, mid + 1, right, index, value);

                tree[root] = Math.max(tree[2 * root + 1], tree[2 * root + 2]);
            }
        }
    }

    static void solve(InputReader in, OutputWriter out) {
        int n = in.nextInt();

        long[][] a = new long[n][2];
        for (int i = 0; i < n; i++) {
            int r = in.nextInt();
            int h = in.nextInt();

            a[i][0] = (long) r * r * h;
            a[i][1] = i;
        }

        Arrays.sort(a, new Comparator<long[]>() {
            @Override
            public int compare(long[] o1, long[] o2) {
                if (o1[0] > o2[0])
                    return 1;
                else if (o1[0] < o2[0])
                    return -1;
                else {
                    if (o1[1] > o2[1])
                        return -1;
                    else
                        return 1;
                }
            }
        });

        MaxSegmentTree tree = new MaxSegmentTree(new long[n]);
        for (int i = 0; i < n; i++) {
            long x = tree.max(0, 0, n - 1, 0, (int) a[i][1] - 1);
            tree.update(0, 0, n - 1, (int) a[i][1], x + a[i][0]);
        }
        out.print(tree.max(0, 0, n - 1, 0, n - 1) * Math.PI);
    }

    public static void main(String[] args) {
        InputReader in = new InputReader(System.in);
        OutputWriter out = new OutputWriter(System.out);
        solve(in, out);
        in.close();
        out.close();
    }

    static class InputReader {
        BufferedReader br;
        StringTokenizer st;

        InputReader(InputStream is) {
            br = new BufferedReader(new InputStreamReader(is));
            st = null;
        }

        String nextLine() {
            String line = null;
            try {
                line = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return line;
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                String line = nextLine();
                if (line == null) return null;
                st = new StringTokenizer(line);
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        void close() {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    static class OutputWriter {
        BufferedWriter bw;

        OutputWriter(OutputStream os) {
            bw = new BufferedWriter(new OutputStreamWriter(os));
        }

        void print(int i) {
            print(Integer.toString(i));
        }

        void println(int i) {
            println(Integer.toString(i));
        }

        void print(long l) {
            print(Long.toString(l));
        }

        void println(long l) {
            println(Long.toString(l));
        }

        void print(double d) {
            print(Double.toString(d));
        }

        void println(double d) {
            println(Double.toString(d));
        }

        void print(boolean b) {
            print(Boolean.toString(b));
        }

        void println(boolean b) {
            println(Boolean.toString(b));
        }

        void print(char c) {
            try {
                bw.write(c);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        void println(char c) {
            println(Character.toString(c));
        }

        void print(String s) {
            try {
                bw.write(s);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        void println(String s) {
            print(s);
            print('\n');
        }

        void close() {
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}