#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <limits.h>
using namespace std;
typedef long long ll;

class SegmentTree
{
public:
	SegmentTree(const vector<ll>& elements);
	void add_on_segment(int l, int r, ll delta);
	ll max_query(int l, int r);

private:
	vector<ll> tree;
	vector<ll> delayed;
	int element_cnt;
	int tree_height;
	void apply(int p, int value);
	void build(int p);
	void push(int p);
};

vector<vector<vector<char>>> solutions;
int best_solution = -1;

int evaluate(const vector<vector<char>>& mat)
{
	int sum = 0;
	for (int col = 0; col < mat[0].size(); col++)
	{
		for (int row = 1; row < mat.size(); row++)
		{
			if (mat[row][col] == ' ')
			{
				continue;
			}
			if (mat[row][col] == mat[row-1][col])
			{
				sum++;
			}
		}
	}
	for (int row = 0; row < mat.size(); row++)
	{
		int cnt_in_row = 0;
		for (int col = 0; col < mat[0].size(); col++)
		{
			if (mat[row][col] != ' ')
			{
				cnt_in_row++;
			}
		}
		if (cnt_in_row == 1)
		{
			return -1;
		}
	}
	return sum;
}

void print_mat(const vector<vector<char>>& mat)
{
	for (int i = 0; i < mat.size(); i++)
	{
		for (int j = 0; j < mat[0].size(); j++)
		{
			cout << mat[i][j];
		}
		cout << "\n";
	}
}

void bruteforce(const string& s, int cnt_in_row, int direction, int pos, int row, int col, vector<vector<char>>& current_mat)
{
	if (pos == s.size())
	{
		int val = evaluate(current_mat);
		if (val >= best_solution)
		{
			evaluate(current_mat);
			best_solution = val;
			solutions.clear();
			solutions.push_back(current_mat);
		}
		return;
	}

	char prev_chr = current_mat[row][col + direction];
	current_mat[row][col + direction] = s[pos];
	bruteforce(s, cnt_in_row+1, direction, pos + 1, row, col + direction, current_mat);
	current_mat[row][col + direction] = prev_chr;

	if (cnt_in_row >= 2)
	{
		prev_chr = current_mat[row + 1][col];
		current_mat[row + 1][col] = s[pos];
		bruteforce(s, 1, -direction, pos + 1, row + 1, col, current_mat);
		current_mat[row + 1][col] = prev_chr;
	}
}

string rand_str(int n)
{
	string s(n, ' ');
	for (int i = 0; i < n; i++)
	{
		s[i] = 'a' + rand() % 10;
	}
	return s;
}

ll solve_recursive_dp(const string& s, vector<vector<ll>>& dp, int pos, int len)
{
	int n = s.size();
	if (pos >= n)
		return 0;
	if (dp[pos][len] != -1)
		return dp[pos][len];
	

	string s1 = s.substr(pos, len);
	reverse(s1.begin(), s1.end());
	ll match = 0;
	for (int i = 0; i < 2 && pos+len+i < n; i++)
		match += (s1[i] == s[pos + len + i]);
	
	ll max_val = 0;
	for (int prev_len = 2; prev_len <= n-pos-len; prev_len++)
	{
		max_val = max(max_val,solve_recursive_dp(s, dp, pos + len, prev_len) + match);
		if (prev_len < s1.size() && s1[prev_len] == s[pos+len+prev_len])
		{
			match++;
		}
	}
	dp[pos][len] = max_val;
	return max_val;
}

ll solve_dp_nonrecursive(const string& s)
{
	int n = s.size();
	vector<vector<ll>> dp(n + 1, vector<ll>(n + 1, 0));
	for (int pos = n-2; pos >= 0; pos--)
	{
		for (int len = 2; len <= n - pos - 2; len++)
		{
			string s1 = s.substr(pos, len);
			reverse(s1.begin(), s1.end());
			ll match = 0;
			for (int i = 0; i < 2 && pos + len + i < n; i++)
				match += (s1[i] == s[pos + len + i]);
			ll max_val = 0;
			for (int prev_len = 2; prev_len <= n - pos - len; prev_len++)
			{
				max_val = max(max_val, dp[pos + len][prev_len] + match);
				if (prev_len < s1.size() && s1[prev_len] == s[pos + len + prev_len])
				{
					match++;
				}
			}
			dp[pos][len] = max_val;
		}
	}
	return *max_element(dp[0].begin(), dp[0].end());
}

void update_array(vector<ll>& v, int l, int r, int delta=1)
{
	for (int i = l; i < r; i++)
	{
		v[i] += delta;
	}
}

ll max_query(vector<ll>& v, int l, int r)
{
	ll res = v[l];
	for (int i = l; i < r; i++)
	{
		res = max(res, v[i]);
	}
	return res;
}

ll solve_dp(const string& s)
{
	int n = s.size();
	vector<vector<ll>> dp(n + 1, vector<ll>(n + 1, 0));
	for (int r = n-3; r >= 1; r--)
	{
		ll match = (s[r] == s[r + 1]) + (s[r - 1] == s[r + 2]);
		vector<ll> dp_to_right(n + 1, match);
		for (int prev_len = 2; prev_len <= n - r - 1; prev_len++)
			dp_to_right[prev_len] += dp[r + 1][prev_len];
		auto seg_tree = SegmentTree(dp_to_right);


		for (int len = 2; len <= r+1; len++)
		{
			//dp[r - len + 1][len] = max(dp[r - len + 1][len], max_query(dp_to_right, 2, n - 1));
			dp[r - len + 1][len] = max(dp[r - len + 1][len], seg_tree.max_query(2, n - 1));
			if (r-len >= 0 && r + len + 1 < n && s[r - len] == s[r + len + 1])
			{
				seg_tree.add_on_segment(len + 1, n - 1, 1);
				//update_array(dp_to_right, len+1, n - 1, 1);
			}
		}
	}

	return *max_element(dp[0].begin(), dp[0].end());
}

void test()
{
	srand(time(0));
	for (int t = 0; t < 500000; t++)
	{
		string s = rand_str(10);
		best_solution = -1;
		solutions.clear();
		vector<vector<char>> mat(s.size() + 1, vector<char>(2 * s.size() + 10, ' '));
		bruteforce(s, 0, 1, 0, 0, s.size(), mat);
		vector<vector<ll>> dp(s.size() + 1, vector<ll>(s.size() + 1, -1));
		ll sol = -1;
		for (int i = 2; i < s.size() - 2; i++)
		{
			sol = max(solve_recursive_dp(s, dp, 0, i), sol);
		}
		ll sol2 = solve_dp(s);
		if (sol != best_solution || sol2 != sol || sol2 != best_solution)
		{
			print_mat(solutions[0]);
			solve_dp(s);
			cout << sol << " " << best_solution << "\n";
			cout << s << "\n";
			cout << "wa\n";
		}
		else
		{
			cout << fixed;
			cout << setprecision(2);
			cout << t * 100.0 / 500000 << "\r";
		}
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	string s; cin >> s;
	cout << solve_dp(s) << "\n";

	return 0;
}


SegmentTree::SegmentTree(const vector<ll>& elements)
{
	element_cnt = elements.size();
	delayed = vector<ll>(element_cnt);
	tree = vector<ll>(2 * element_cnt);
	copy(elements.begin(), elements.end(), tree.begin() + element_cnt);
	for (int i = element_cnt - 1; i > 0; i--)
		tree[i] = max(tree[i * 2], tree[i * 2 + 1]);
	
	int mask = 1;
	tree_height = 1;
	for (int i = 0; i < 30; i++)
	{
		if (mask & element_cnt)
		{
			tree_height = max(tree_height, i + 1);
		}
		mask <<= 1;
	}
}

void SegmentTree::add_on_segment(int l, int r, ll delta)
{
	l += element_cnt, r += element_cnt;
	int l0 = l, r0 = r;
	for (; l < r; l >>= 1, r >>= 1) {
		if (l & 1) apply(l++, delta);
		if (r & 1) apply(--r, delta);
	}
	build(l0);
	build(r0 - 1);
}

ll SegmentTree::max_query(int l, int r)
{
	l += element_cnt, r += element_cnt;
	push(l);
	push(r - 1);
	ll res = LLONG_MIN;
	for (; l < r; l >>= 1, r >>= 1) {
		if (l & 1) res = max(res, tree[l++]);
		if (r & 1) res = max(tree[--r], res);
	}
	return res;
}


void SegmentTree::apply(int p, int value) {
	tree[p] += value;
	if (p < element_cnt) delayed[p] += value;
}

void SegmentTree::build(int p) {
	while (p > 1) p >>= 1, tree[p] = max(tree[p << 1], tree[p << 1 | 1]) + delayed[p];
}

void SegmentTree::push(int p) {
	for (int s = tree_height; s > 0; --s) {
		int i = p >> s;
		if (delayed[i] != 0) {
			apply(i << 1, delayed[i]);
			apply(i << 1 | 1, delayed[i]);
			delayed[i] = 0;
		}
	}
}