#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <functional>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <bitset>

using namespace std;

void test() {
	int n = 9, i, j, k;

	vector<int> ct(n);

	for (i = 1; i <= n; i++) {
		cout << "Length = " << setw(3) << i << ": ";
		ct.assign(n, 0);
		for (j = 0; j + i <= n; j++) {
			for (k = 0; k < i; k++) ct[j + k]++;
		}
		for (auto p : ct) cout << setw(6) << p;
		cout << "\n";
	}
}

// Union-Find Disjoint Sets Library written in OOP manner, using both path compression and union by rank heuristics
// initialize: UnionFind UF(N)

class UnionFind {                                              // OOP style
private:
	vector<int> p, rank, setSize;
	// p = path toward the root of disjoint set; p[i] = i means it is root
	// rank = upper bound of the actual height of the tree; not reliable as accurate measure
	// setSize = size of each disjoint set

	int numSets;
public:
	UnionFind(int N) {
		setSize.assign(N, 1);
		numSets = N;
		rank.assign(N, 0);
		p.assign(N, 0);
		for (int i = 0; i < N; i++) p[i] = i;	// each belongs to its own set
	}

	int findSet(int i) {
		return (p[i] == i) ? i : (p[i] = findSet(p[i]));		// path compression: cut short of the path if possible
	}

	bool isSameSet(int i, int j) {
		return findSet(i) == findSet(j);
	}

	void unionSet(int i, int j) {
		if (!isSameSet(i, j)) {
			numSets--;
			int x = findSet(i), y = findSet(j);
			// rank is used to keep the tree short
			if (rank[x] > rank[y]) { p[y] = x; setSize[x] += setSize[y]; }
			else {
				p[x] = y; setSize[y] += setSize[x];
				if (rank[x] == rank[y]) rank[y]++;
			}
		}
	}

	int numDisjointSets() {		// # of disjoint sets
		return numSets;
	}

	int sizeOfSet(int i) {		// size of set
		return setSize[findSet(i)];
	}
};

void query(int a, int b, UnionFind &uf) {
	int n = b + 1 - a, i;
	for (i = a; i <= b; i++) uf.unionSet(i, b - (i - a));
}

vector<vector<int>> queryme(int a, int b) {
	int n = b + 1 - a, m = n * (n + 1) / 2, i, j, k, x;
	vector<vector<int>> ct(n + 1, vector<int>(26, 0));
	string s;

	cout << "? " << a << " " << b << "\n";
	cout.flush();
	for (i = 0; i < m; i++) {
		cin >> s;
		x = s.length();
		for (auto ch : s) ct[x][ch - 'a']++;
	}

	vector<vector<int>> ans((n + 1) / 2 + 1);
	vector<int> sct(26, 0);

	for (i = (n + 1) / 2; i >= 1; i--) {
		vector<int> cc(26);
		for (j = 0; j < 26; j++) cc[j] = ct[i][j] - ct[i - 1][j] - sct[j];
		for (j = 0; j < 26; j++) {
			sct[j] += cc[j];
			for (k = 0; k < cc[j]; k++)
				ans[i].push_back(j);
		}
	}
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);

	int n, i, j, k;
	string ans;

	cin >> n;
	vector<vector<int>> q[3];

	if (n == 1) {
		q[0] = queryme(1, n);
		ans = " ";
		ans[0] = 'a' + q[0][1][0];
	}
	else if (n == 2) {
		q[0] = queryme(1, 1);
		q[1] = queryme(2, 2);
		ans = "  ";
		ans[0] = 'a' + q[0][1][0];
		ans[1] = 'a' + q[1][1][0];
	}
	else {
		vector<int> a(n + 1, -1);
		map<pair<int, int>, pair<int, int>> pp;
		queue<int> qq;
		int x = n / 2;

		if (n % 2 == 0) {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size(); i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };

			q[1] = queryme(1, x);
			for (i = 1; i < q[1].size(); i++) {
				if (q[1][i].size() == 1) {
					a[i] = q[2][i][0];
					qq.push(i);
				}
				else pp[{i, x + 1 - i}] = { q[1][i][0], q[1][i][1] };
			}

			q[2] = queryme(x, n);
			for (i = 1; i < q[2].size(); i++) {
				if (q[2][i].size() == 1) {
					a[i + x - 1] = q[2][i][0];
					qq.push(i + x - 1);
				}
				else pp[{i + x - 1, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
			}
		}
		else {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size() - 1; i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };
			a[(n + 1) / 2] = q[0][(n + 1) / 2][0];
			qq.push((n + 1) / 2);

			q[1] = queryme(1, x);
			for (i = 1; i < q[1].size(); i++) {
				if (q[1][i].size() == 1) {
					a[i] = q[2][i][0];
					qq.push(i);
				}
				else pp[{i, x + 1 - i}] = { q[1][i][0], q[1][i][1] };
			}

			q[2] = queryme(x + 1, n);
			for (i = 1; i < q[2].size(); i++) {
				if (q[2][i].size() == 1) {
					a[i + x - 1] = q[2][i][0];
					qq.push(i + x - 1);
				}
				else pp[{i + x - 1, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
			}
		}

		while (!qq.empty()) {
			int id = qq.front();
			qq.pop();

			for (auto p : pp) {
				if ((p.first.first == id) || (p.first.second == id)) {
					int x = p.first.first + p.first.second - id;
					if (a[x] == -1) {
						a[x] = p.second.first + p.second.second - a[id];
						qq.push(x);
					}
				}
			}
		}

		ans = "";
		for (i = 1; i <= n; i++) {
			char ch = 'a' + a[i];
			ans = ans + ch;
		}
	}

	cout << "! " << ans << "\n";
	cout.flush();

	return 0;

	for (n = 4; n <= 100; n++) {
		UnionFind uf(n + 1);
		int x = n / 2;

		if (n % 2 == 0) {
			x++;
			query(1, n, uf);
			query(1, x, uf);
			query(x, n, uf);
		}
		else {
			//if (x % 2 == 1) x++;
			query(1, n, uf);
			query(1, x, uf);
			query(x + 1, n, uf);
		}

		int ct = 0;
		for (i = 1; i <= n; i++) {
			if (uf.findSet(i) == i) {
				//for (j = 1; j <= n; j++) {
				//	if (uf.findSet(j) == i) cout << j << " ";
				//}
				//cout << "\n";
				ct++;
			}
		}
		//cout << "Total Sets = " << ct << "\n";
		if (ct != 1) {
			break;
		}

	}

	return 0;
}


#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <functional>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <bitset>

using namespace std;

void test() {
	int n = 9, i, j, k;

	vector<int> ct(n);

	for (i = 1; i <= n; i++) {
		cout << "Length = " << setw(3) << i << ": ";
		ct.assign(n, 0);
		for (j = 0; j + i <= n; j++) {
			for (k = 0; k < i; k++) ct[j + k]++;
		}
		for (auto p : ct) cout << setw(6) << p;
		cout << "\n";
	}
}

// Union-Find Disjoint Sets Library written in OOP manner, using both path compression and union by rank heuristics
// initialize: UnionFind UF(N)

class UnionFind {                                              // OOP style
private:
	vector<int> p, rank, setSize;
	// p = path toward the root of disjoint set; p[i] = i means it is root
	// rank = upper bound of the actual height of the tree; not reliable as accurate measure
	// setSize = size of each disjoint set

	int numSets;
public:
	UnionFind(int N) {
		setSize.assign(N, 1);
		numSets = N;
		rank.assign(N, 0);
		p.assign(N, 0);
		for (int i = 0; i < N; i++) p[i] = i;	// each belongs to its own set
	}

	int findSet(int i) {
		return (p[i] == i) ? i : (p[i] = findSet(p[i]));		// path compression: cut short of the path if possible
	}

	bool isSameSet(int i, int j) {
		return findSet(i) == findSet(j);
	}

	void unionSet(int i, int j) {
		if (!isSameSet(i, j)) {
			numSets--;
			int x = findSet(i), y = findSet(j);
			// rank is used to keep the tree short
			if (rank[x] > rank[y]) { p[y] = x; setSize[x] += setSize[y]; }
			else {
				p[x] = y; setSize[y] += setSize[x];
				if (rank[x] == rank[y]) rank[y]++;
			}
		}
	}

	int numDisjointSets() {		// # of disjoint sets
		return numSets;
	}

	int sizeOfSet(int i) {		// size of set
		return setSize[findSet(i)];
	}
};

void query(int a, int b, UnionFind &uf) {
	int n = b + 1 - a, i;
	for (i = a; i <= b; i++) uf.unionSet(i, b - (i - a));
}

vector<vector<int>> queryme(int a, int b) {
	int n = b + 1 - a, m = n * (n + 1) / 2, i, j, k, x;
	vector<vector<int>> ct(n + 1, vector<int>(26, 0));
	string s;

	cout << "? " << a << " " << b << "\n";
	cout.flush();
	for (i = 0; i < m; i++) {
		cin >> s;
		x = s.length();
		for (auto ch : s) ct[x][ch - 'a']++;
	}

	vector<vector<int>> ans((n + 1) / 2 + 1);
	vector<int> sct(26, 0);

	for (i = (n + 1) / 2; i >= 1; i--) {
		vector<int> cc(26);
		for (j = 0; j < 26; j++) cc[j] = ct[i][j] - ct[i - 1][j] - sct[j];
		for (j = 0; j < 26; j++) {
			sct[j] += cc[j];
			for (k = 0; k < cc[j]; k++)
				ans[i].push_back(j);
		}
	}
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);

	int n, i, j, k;
	string ans;

	cin >> n;
	vector<vector<int>> q[3];

	if (n == 1) {
		q[0] = queryme(1, n);
		ans = " ";
		ans[0] = 'a' + q[0][1][0];
	}
	else if (n == 2) {
		q[0] = queryme(1, 1);
		q[1] = queryme(2, 2);
		ans = "  ";
		ans[0] = 'a' + q[0][1][0];
		ans[1] = 'a' + q[1][1][0];
	}
	else {
		vector<int> a(n + 1, -1);
		map<pair<int, int>, pair<int, int>> pp;
		queue<int> qq;

		if (n % 2 == 0) {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size(); i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };

			q[1] = queryme(1, 3);
			pp[{1, 3}] = { q[1][1][0], q[1][1][1] };
			a[2] = q[1][2][0];

			q[2] = queryme(3, n);
			for (i = 1; i < q[2].size(); i++) pp[{i + 2, n + 1 - i}] = { q[2][i][0], q[2][i][1] };

			qq.push(2);
		}
		else {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size() - 1; i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };
			a[(n + 1) / 2] = q[0][(n + 1) / 2][0];
			qq.push((n + 1) / 2);

			q[1] = queryme(1, 2);
			pp[{1, 2}] = { q[1][1][0], q[1][1][1] };

			q[2] = queryme(2, n);
			for (i = 1; i < q[2].size(); i++) pp[{i + 1, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
		}

		while (!qq.empty()) {
			int id = qq.front();
			qq.pop();

			for (auto p : pp) {
				if ((p.first.first == id) || (p.first.second == id)) {
					int x = p.first.first + p.first.second - id;
					if (a[x] == -1) {
						a[x] = p.second.first + p.second.second - a[id];
						qq.push(x);
					}
				}
			}
		}

		ans = "";
		for (i = 1; i <= n; i++) {
			char ch = 'a' + a[i];
			ans = ans + ch;
		}
	}

	cout << "! " << ans << "\n";
	cout.flush();

	//for (n = 3; n <= 100; n++) {
	//	UnionFind uf(n + 1);

	//	if (n % 2 == 0) {
	//		query(1, n, uf);
	//		query(1, 3, uf);
	//		query(3, n, uf);
	//	}
	//	else {
	//		query(1, n, uf);
	//		query(1, 2, uf);
	//		query(2, n, uf);
	//	}

	//	int ct = 0;
	//	for (i = 1; i <= n; i++) {
	//		if (uf.findSet(i) == i) {
	//			//for (j = 1; j <= n; j++) {
	//			//	if (uf.findSet(j) == i) cout << j << " ";
	//			//}
	//			//cout << "\n";
	//			ct++;
	//		}
	//	}
	//	//cout << "Total Sets = " << ct << "\n";
	//	if (ct != 1) {
	//		break;
	//	}

	//}

	return 0;
}


#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <functional>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <bitset>

using namespace std;

void test() {
	int n = 9, i, j, k;

	vector<int> ct(n);

	for (i = 1; i <= n; i++) {
		cout << "Length = " << setw(3) << i << ": ";
		ct.assign(n, 0);
		for (j = 0; j + i <= n; j++) {
			for (k = 0; k < i; k++) ct[j + k]++;
		}
		for (auto p : ct) cout << setw(6) << p;
		cout << "\n";
	}
}

// Union-Find Disjoint Sets Library written in OOP manner, using both path compression and union by rank heuristics
// initialize: UnionFind UF(N)

class UnionFind {                                              // OOP style
private:
	vector<int> p, rank, setSize;
	// p = path toward the root of disjoint set; p[i] = i means it is root
	// rank = upper bound of the actual height of the tree; not reliable as accurate measure
	// setSize = size of each disjoint set

	int numSets;
public:
	UnionFind(int N) {
		setSize.assign(N, 1);
		numSets = N;
		rank.assign(N, 0);
		p.assign(N, 0);
		for (int i = 0; i < N; i++) p[i] = i;	// each belongs to its own set
	}

	int findSet(int i) {
		return (p[i] == i) ? i : (p[i] = findSet(p[i]));		// path compression: cut short of the path if possible
	}

	bool isSameSet(int i, int j) {
		return findSet(i) == findSet(j);
	}

	void unionSet(int i, int j) {
		if (!isSameSet(i, j)) {
			numSets--;
			int x = findSet(i), y = findSet(j);
			// rank is used to keep the tree short
			if (rank[x] > rank[y]) { p[y] = x; setSize[x] += setSize[y]; }
			else {
				p[x] = y; setSize[y] += setSize[x];
				if (rank[x] == rank[y]) rank[y]++;
			}
		}
	}

	int numDisjointSets() {		// # of disjoint sets
		return numSets;
	}

	int sizeOfSet(int i) {		// size of set
		return setSize[findSet(i)];
	}
};

void query(int a, int b, UnionFind &uf) {
	int n = b + 1 - a, i;
	for (i = a; i <= b; i++) uf.unionSet(i, b - (i - a));
}

vector<vector<int>> queryme(int a, int b) {
	int n = b + 1 - a, m = n * (n + 1) / 2, i, j, k, x;
	vector<vector<int>> ct(n + 1, vector<int>(26, 0));
	string s;

	cout << "? " << a << " " << b << "\n";
	cout.flush();
	for (i = 0; i < m; i++) {
		cin >> s;
		x = s.length();
		for (auto ch : s) ct[x][ch - 'a']++;
	}

	vector<vector<int>> ans((n + 1) / 2 + 1);
	vector<int> sct(26, 0);

	for (i = (n + 1) / 2; i >= 1; i--) {
		vector<int> cc(26);
		for (j = 0; j < 26; j++) cc[j] = ct[i][j] - ct[i - 1][j] - sct[j];
		for (j = 0; j < 26; j++) {
			sct[j] += cc[j];
			for (k = 0; k < cc[j]; k++)
				ans[i].push_back(j);
		}
	}
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);

	int n, i, j, k;
	string ans;

	cin >> n;
	vector<vector<int>> q[3];

	if (n == 1) {
		q[0] = queryme(1, n);
		ans = " ";
		ans[0] = 'a' + q[0][1][0];
	}
	else if (n == 2) {
		q[0] = queryme(1, 1);
		q[1] = queryme(2, 2);
		ans = "  ";
		ans[0] = 'a' + q[0][1][0];
		ans[1] = 'a' + q[1][1][0];
	}
	else {
		vector<int> a(n + 1, -1);
		map<pair<int, int>, pair<int, int>> pp;
		queue<int> qq;
		int x = n / 2;

		if (n % 2 == 0) {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size(); i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };

			q[1] = queryme(1, x);
			for (i = 1; i < q[1].size(); i++) {
				if (q[1][i].size() == 1) {
					a[i] = q[2][i][0];
					qq.push(i);
				}
				else pp[{i, x + 1 - i}] = { q[1][i][0], q[1][i][1] };
			}

			q[2] = queryme(x, n);
			for (i = 1; i < q[2].size(); i++) {
				if (q[2][i].size() == 1) {
					a[i + x - 1] = q[1][i][0];
					qq.push(i + x - 1);
				}
				else pp[{i + x - 1, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
			}
		}
		else {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size() - 1; i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };
			a[(n + 1) / 2] = q[0][(n + 1) / 2][0];
			qq.push((n + 1) / 2);

			q[1] = queryme(1, x);
			for (i = 1; i < q[1].size(); i++) {
				if (q[1][i].size() == 1) {
					a[i] = q[1][i][0];
					qq.push(i);
				}
				else pp[{i, x + 1 - i}] = { q[1][i][0], q[1][i][1] };
			}

			q[2] = queryme(x + 1, n);
			for (i = 1; i < q[2].size(); i++) {
				if (q[2][i].size() == 1) {
					a[i + x - 1] = q[2][i][0];
					qq.push(i + x - 1);
				}
				else pp[{i + x - 1, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
			}
		}

		while (!qq.empty()) {
			int id = qq.front();
			qq.pop();

			for (auto p : pp) {
				if ((p.first.first == id) || (p.first.second == id)) {
					int x = p.first.first + p.first.second - id;
					if (a[x] == -1) {
						a[x] = p.second.first + p.second.second - a[id];
						qq.push(x);
					}
				}
			}
		}

		ans = "";
		for (i = 1; i <= n; i++) {
			char ch = 'a' + a[i];
			ans = ans + ch;
		}
	}

	cout << "! " << ans << "\n";
	cout.flush();

	return 0;

	for (n = 4; n <= 100; n++) {
		UnionFind uf(n + 1);
		int x = n / 2;

		if (n % 2 == 0) {
			x++;
			query(1, n, uf);
			query(1, x, uf);
			query(x, n, uf);
		}
		else {
			//if (x % 2 == 1) x++;
			query(1, n, uf);
			query(1, x, uf);
			query(x + 1, n, uf);
		}

		int ct = 0;
		for (i = 1; i <= n; i++) {
			if (uf.findSet(i) == i) {
				//for (j = 1; j <= n; j++) {
				//	if (uf.findSet(j) == i) cout << j << " ";
				//}
				//cout << "\n";
				ct++;
			}
		}
		//cout << "Total Sets = " << ct << "\n";
		if (ct != 1) {
			break;
		}

	}

	return 0;
}


#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <functional>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <bitset>

using namespace std;

void test() {
	int n = 9, i, j, k;

	vector<int> ct(n);

	for (i = 1; i <= n; i++) {
		cout << "Length = " << setw(3) << i << ": ";
		ct.assign(n, 0);
		for (j = 0; j + i <= n; j++) {
			for (k = 0; k < i; k++) ct[j + k]++;
		}
		for (auto p : ct) cout << setw(6) << p;
		cout << "\n";
	}
}

// Union-Find Disjoint Sets Library written in OOP manner, using both path compression and union by rank heuristics
// initialize: UnionFind UF(N)

class UnionFind {                                              // OOP style
private:
	vector<int> p, rank, setSize;
	// p = path toward the root of disjoint set; p[i] = i means it is root
	// rank = upper bound of the actual height of the tree; not reliable as accurate measure
	// setSize = size of each disjoint set

	int numSets;
public:
	UnionFind(int N) {
		setSize.assign(N, 1);
		numSets = N;
		rank.assign(N, 0);
		p.assign(N, 0);
		for (int i = 0; i < N; i++) p[i] = i;	// each belongs to its own set
	}

	int findSet(int i) {
		return (p[i] == i) ? i : (p[i] = findSet(p[i]));		// path compression: cut short of the path if possible
	}

	bool isSameSet(int i, int j) {
		return findSet(i) == findSet(j);
	}

	void unionSet(int i, int j) {
		if (!isSameSet(i, j)) {
			numSets--;
			int x = findSet(i), y = findSet(j);
			// rank is used to keep the tree short
			if (rank[x] > rank[y]) { p[y] = x; setSize[x] += setSize[y]; }
			else {
				p[x] = y; setSize[y] += setSize[x];
				if (rank[x] == rank[y]) rank[y]++;
			}
		}
	}

	int numDisjointSets() {		// # of disjoint sets
		return numSets;
	}

	int sizeOfSet(int i) {		// size of set
		return setSize[findSet(i)];
	}
};

void query(int a, int b, UnionFind &uf) {
	int n = b + 1 - a, i;
	for (i = a; i <= b; i++) uf.unionSet(i, b - (i - a));
}

vector<vector<int>> queryme(int a, int b) {
	int n = b + 1 - a, m = n * (n + 1) / 2, i, j, k, x;
	vector<vector<int>> ct(n + 1, vector<int>(26, 0));
	string s;

	cout << "? " << a << " " << b << "\n";
	cout.flush();
	for (i = 0; i < m; i++) {
		cin >> s;
		x = s.length();
		for (auto ch : s) ct[x][ch - 'a']++;
	}

	vector<vector<int>> ans((n + 1) / 2 + 1);
	vector<int> sct(26, 0);

	for (i = (n + 1) / 2; i >= 1; i--) {
		vector<int> cc(26);
		for (j = 0; j < 26; j++) cc[j] = ct[i][j] - ct[i - 1][j] - sct[j];
		for (j = 0; j < 26; j++) {
			sct[j] += cc[j];
			for (k = 0; k < cc[j]; k++)
				ans[i].push_back(j);
		}
	}
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);

	int n, i, j, k;
	string ans;

	cin >> n;
	vector<vector<int>> q[3];

	if (n == 1) {
		q[0] = queryme(1, n);
		ans = " ";
		ans[0] = 'a' + q[0][1][0];
	}
	else if (n == 2) {
		q[0] = queryme(1, 1);
		q[1] = queryme(2, 2);
		ans = "  ";
		ans[0] = 'a' + q[0][1][0];
		ans[1] = 'a' + q[1][1][0];
	}
	else {
		vector<int> a(n + 1, -1);
		map<pair<int, int>, pair<int, int>> pp;
		queue<int> qq;
		int x = n / 2;

		if (n % 2 == 0) {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size(); i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };

			q[1] = queryme(1, x);
			for (i = 1; i < q[1].size(); i++) {
				if (q[1][i].size() == 1) {
					a[i] = q[1][i][0];
					qq.push(i);
				}
				else pp[{i, x + 1 - i}] = { q[1][i][0], q[1][i][1] };
			}

			q[2] = queryme(x, n);
			for (i = 1; i < q[2].size(); i++) {
				if (q[2][i].size() == 1) {
					a[i + x - 1] = q[2][i][0];
					qq.push(i + x - 1);
				}
				else pp[{i + x - 1, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
			}
		}
		else {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size() - 1; i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };
			a[(n + 1) / 2] = q[0][(n + 1) / 2][0];
			qq.push((n + 1) / 2);

			q[1] = queryme(1, x);
			for (i = 1; i < q[1].size(); i++) {
				if (q[1][i].size() == 1) {
					a[i] = q[1][i][0];
					qq.push(i);
				}
				else pp[{i, x + 1 - i}] = { q[1][i][0], q[1][i][1] };
			}

			q[2] = queryme(x + 1, n);
			for (i = 1; i < q[2].size(); i++) {
				if (q[2][i].size() == 1) {
					a[i + x - 1] = q[2][i][0];
					qq.push(i + x - 1);
				}
				else pp[{i + x - 1, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
			}
		}

		while (!qq.empty()) {
			int id = qq.front();
			qq.pop();

			for (auto p : pp) {
				if ((p.first.first == id) || (p.first.second == id)) {
					int x = p.first.first + p.first.second - id;
					if (a[x] == -1) {
						a[x] = p.second.first + p.second.second - a[id];
						qq.push(x);
					}
				}
			}
		}

		ans = "";
		for (i = 1; i <= n; i++) {
			char ch = 'a' + a[i];
			ans = ans + ch;
		}
	}

	cout << "! " << ans << "\n";
	cout.flush();

	return 0;

	for (n = 4; n <= 100; n++) {
		UnionFind uf(n + 1);
		int x = n / 2;

		if (n % 2 == 0) {
			x++;
			query(1, n, uf);
			query(1, x, uf);
			query(x, n, uf);
		}
		else {
			//if (x % 2 == 1) x++;
			query(1, n, uf);
			query(1, x, uf);
			query(x + 1, n, uf);
		}

		int ct = 0;
		for (i = 1; i <= n; i++) {
			if (uf.findSet(i) == i) {
				//for (j = 1; j <= n; j++) {
				//	if (uf.findSet(j) == i) cout << j << " ";
				//}
				//cout << "\n";
				ct++;
			}
		}
		//cout << "Total Sets = " << ct << "\n";
		if (ct != 1) {
			break;
		}

	}

	return 0;
}


#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <functional>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <bitset>

using namespace std;

void test() {
	int n = 9, i, j, k;

	vector<int> ct(n);

	for (i = 1; i <= n; i++) {
		cout << "Length = " << setw(3) << i << ": ";
		ct.assign(n, 0);
		for (j = 0; j + i <= n; j++) {
			for (k = 0; k < i; k++) ct[j + k]++;
		}
		for (auto p : ct) cout << setw(6) << p;
		cout << "\n";
	}
}

// Union-Find Disjoint Sets Library written in OOP manner, using both path compression and union by rank heuristics
// initialize: UnionFind UF(N)

class UnionFind {                                              // OOP style
private:
	vector<int> p, rank, setSize;
	// p = path toward the root of disjoint set; p[i] = i means it is root
	// rank = upper bound of the actual height of the tree; not reliable as accurate measure
	// setSize = size of each disjoint set

	int numSets;
public:
	UnionFind(int N) {
		setSize.assign(N, 1);
		numSets = N;
		rank.assign(N, 0);
		p.assign(N, 0);
		for (int i = 0; i < N; i++) p[i] = i;	// each belongs to its own set
	}

	int findSet(int i) {
		return (p[i] == i) ? i : (p[i] = findSet(p[i]));		// path compression: cut short of the path if possible
	}

	bool isSameSet(int i, int j) {
		return findSet(i) == findSet(j);
	}

	void unionSet(int i, int j) {
		if (!isSameSet(i, j)) {
			numSets--;
			int x = findSet(i), y = findSet(j);
			// rank is used to keep the tree short
			if (rank[x] > rank[y]) { p[y] = x; setSize[x] += setSize[y]; }
			else {
				p[x] = y; setSize[y] += setSize[x];
				if (rank[x] == rank[y]) rank[y]++;
			}
		}
	}

	int numDisjointSets() {		// # of disjoint sets
		return numSets;
	}

	int sizeOfSet(int i) {		// size of set
		return setSize[findSet(i)];
	}
};

void query(int a, int b, UnionFind &uf) {
	int n = b + 1 - a, i;
	for (i = a; i <= b; i++) uf.unionSet(i, b - (i - a));
}

vector<vector<int>> queryme(int a, int b) {
	int n = b + 1 - a, m = n * (n + 1) / 2, i, j, k, x;
	vector<vector<int>> ct(n + 1, vector<int>(26, 0));
	string s;

	cout << "? " << a << " " << b << "\n";
	cout.flush();
	for (i = 0; i < m; i++) {
		cin >> s;
		x = s.length();
		for (auto ch : s) ct[x][ch - 'a']++;
	}

	vector<vector<int>> ans((n + 1) / 2 + 1);
	vector<int> sct(26, 0);

	for (i = (n + 1) / 2; i >= 1; i--) {
		vector<int> cc(26);
		for (j = 0; j < 26; j++) cc[j] = ct[i][j] - ct[i - 1][j] - sct[j];
		for (j = 0; j < 26; j++) {
			sct[j] += cc[j];
			for (k = 0; k < cc[j]; k++)
				ans[i].push_back(j);
		}
	}
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);

	int n, i, j, k;
	string ans;

	cin >> n;
	vector<vector<int>> q[3];

	if (n == 1) {
		q[0] = queryme(1, n);
		ans = " ";
		ans[0] = 'a' + q[0][1][0];
	}
	else if (n == 2) {
		q[0] = queryme(1, 1);
		q[1] = queryme(2, 2);
		ans = "  ";
		ans[0] = 'a' + q[0][1][0];
		ans[1] = 'a' + q[1][1][0];
	}
	else {
		vector<int> a(n + 1, -1);
		map<pair<int, int>, pair<int, int>> pp;
		queue<int> qq;
		int x = n / 2;

		if (n % 2 == 0) {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size(); i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };

			q[1] = queryme(1, x);
			for (i = 1; i < q[1].size(); i++) {
				if (q[1][i].size() == 1) {
					a[i] = q[1][i][0];
					qq.push(i);
				}
				else pp[{i, x + 1 - i}] = { q[1][i][0], q[1][i][1] };
			}

			q[2] = queryme(x, n);
			for (i = 1; i < q[2].size(); i++) {
				if (q[2][i].size() == 1) {
					a[i + x - 1] = q[2][i][0];
					qq.push(i + x - 1);
				}
				else pp[{i + x - 1, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
			}
		}
		else {
			q[0] = queryme(1, n);
			for (i = 1; i < q[0].size() - 1; i++) pp[{i, n + 1 - i}] = { q[0][i][0], q[0][i][1] };
			a[(n + 1) / 2] = q[0][(n + 1) / 2][0];
			qq.push((n + 1) / 2);

			q[1] = queryme(1, x);
			for (i = 1; i < q[1].size(); i++) {
				if (q[1][i].size() == 1) {
					a[i] = q[1][i][0];
					qq.push(i);
				}
				else pp[{i, x + 1 - i}] = { q[1][i][0], q[1][i][1] };
			}

			q[2] = queryme(x + 1, n);
			for (i = 1; i < q[2].size(); i++) {
				if (q[2][i].size() == 1) {
					a[i + x] = q[2][i][0];
					qq.push(i + x - 1);
				}
				else pp[{i + x, n + 1 - i}] = { q[2][i][0], q[2][i][1] };
			}
		}

		while (!qq.empty()) {
			int id = qq.front();
			qq.pop();

			for (auto p : pp) {
				if ((p.first.first == id) || (p.first.second == id)) {
					int x = p.first.first + p.first.second - id;
					if (a[x] == -1) {
						a[x] = p.second.first + p.second.second - a[id];
						qq.push(x);
					}
				}
			}
		}

		ans = "";
		for (i = 1; i <= n; i++) {
			char ch = 'a' + a[i];
			ans = ans + ch;
		}
	}

	cout << "! " << ans << "\n";
	cout.flush();

	return 0;

	for (n = 4; n <= 100; n++) {
		UnionFind uf(n + 1);
		int x = n / 2;

		if (n % 2 == 0) {
			x++;
			query(1, n, uf);
			query(1, x, uf);
			query(x, n, uf);
		}
		else {
			//if (x % 2 == 1) x++;
			query(1, n, uf);
			query(1, x, uf);
			query(x + 1, n, uf);
		}

		int ct = 0;
		for (i = 1; i <= n; i++) {
			if (uf.findSet(i) == i) {
				//for (j = 1; j <= n; j++) {
				//	if (uf.findSet(j) == i) cout << j << " ";
				//}
				//cout << "\n";
				ct++;
			}
		}
		//cout << "Total Sets = " << ct << "\n";
		if (ct != 1) {
			break;
		}

	}

	return 0;
}


#include <iostream>
#include <iomanip>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cassert>
#include <string>
#include <set>
#include <map>
#include <random>
#include <bitset>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <deque>
#include <queue>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()

using namespace std;
using ll = long long;
using ul = unsigned long long;
using ld = long double;

vector<string> q(int l, int r) {
    if (l == r)
        return {};
    int d = r - l;
    cout << "? " << l + 1 << ' ' << r << endl;
    vector<string> ans(d * (d + 1) / 2);
    rep(i, d * (d + 1) / 2) {
        cin >> ans[i];
        sort(all(ans[i]));
    }
    return ans;
}

string solve(int n) {
    if (n < 1)
        return "";
    vector<string> a = q(0, n), b = q(1, n);
    multiset<string> ss;
    for (string i : a)
        ss.insert(i);
    for (string i : b)
        ss.erase(ss.find(i));
    vector<string> d(n + 1);
    for (string i : ss)
        d[i.size()] = i;
    string ans(n, ' ');
    rep(i, n) {
        multiset<char> ms;
        for (char c : d[i + 1])
            ms.insert(c);
        for (char c : d[i])
            ms.erase(ms.find(c));
        ans[i] = *ms.begin();
    }
    return ans;
}

int main() {
#ifdef ONPC
    freopen("a.in", "r", stdin);
#endif
	int n;
    cin >> n;
    string ans = solve(n / 2) + string((n + 1) / 2, ' ');
    vector<string> aa = q(0, n);
    vector<multiset<string>> a(n + 1);
    for (string i : aa)
        a[i.size()].insert(i);
    multiset<char> ms, cs, fs;
    for (char c : *a[n].begin())
        ms.insert(c);
    fs = ms;
    for (int i = n - 1; i >= n / 2; i--) {
        cs = ms;
        rep(i, n / 2) {
            cs.erase(cs.find(ans[i]));
            string ws;
            for (char c : cs)
                ws.push_back(c);
            a[ws.size()].erase(a[ws.size()].find(ws));
        }
        cs = fs;
        for (char c : *a[i].begin())
            cs.erase(cs.find(c));
        ans[i] = *cs.begin();
        ms.erase(ms.find(ans[i]));
    }
    cout << "! " << ans;
    cout << endl;
}


#include <iostream>
#include <iomanip>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cassert>
#include <string>
#include <set>
#include <map>
#include <random>
#include <bitset>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <deque>
#include <queue>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()

using namespace std;
using ll = long long;
using ul = unsigned long long;
using ld = long double;

vector<string> q(int l, int r) {
    if (l == r)
        return {};
    int d = r - l;
    cout << "? " << l + 1 << ' ' << r << endl;
    vector<string> ans(d * (d + 1) / 2);
    rep(i, d * (d + 1) / 2) {
        cin >> ans[i];
        if (ans[i][0] == '-')
            exit(0);
        sort(all(ans[i]));
    }
    return ans;
}

string solve(int n) {
    if (n < 1)
        return "";
    vector<string> a = q(0, n), b = q(1, n);
    multiset<string> ss;
    for (string i : a)
        ss.insert(i);
    for (string i : b)
        ss.erase(ss.find(i));
    vector<string> d(n + 1);
    for (string i : ss)
        d[i.size()] = i;
    string ans(n, ' ');
    rep(i, n) {
        multiset<char> ms;
        for (char c : d[i + 1])
            ms.insert(c);
        for (char c : d[i])
            ms.erase(ms.find(c));
        ans[i] = *ms.begin();
    }
    return ans;
}

int main() {
#ifdef ONPC
    freopen("a.in", "r", stdin);
#endif
	int n;
    cin >> n;
    string ans = solve((n + 1) / 2) + string(n / 2, ' ');
    vector<string> aa = q(0, n);
    vector<multiset<string>> a(n + 1);
    for (string i : aa)
        a[i.size()].insert(i);
    multiset<char> ms, cs;
    for (char c : *a[n].begin())
        ms.insert(c);
    for (int i = n - 1; i >= (n + 1) / 2; i--) {
        cs = ms;
        rep(j, (n + 1) / 2 + 1) {
            string ws;
            for (char c : cs)
                ws.push_back(c);
            a[ws.size()].erase(a[ws.size()].find(ws));
            if (j < (n + 1) / 2)
                cs.erase(cs.find(ans[j]));
        }
        cs = ms;
        for (char c : *a[i].begin())
            cs.erase(cs.find(c));
        ans[i] = *cs.begin();
        ms.erase(ms.find(ans[i]));
    }
    cout << "! " << ans;
    cout << endl;
}


#include <iostream>
#include <iomanip>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cassert>
#include <string>
#include <set>
#include <map>
#include <random>
#include <bitset>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <deque>
#include <queue>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()

using namespace std;
using ll = long long;
using ul = unsigned long long;
using ld = long double;

vector<string> q(int l, int r) {
    if (l == r)
        return {};
    int d = r - l;
    cout << "? " << l + 1 << ' ' << r << endl;
    vector<string> ans(d * (d + 1) / 2);
    rep(i, d * (d + 1) / 2) {
        cin >> ans[i];
        sort(all(ans[i]));
    }
    return ans;
}

string solve(int n) {
    if (n < 1)
        return "";
    vector<string> a = q(0, n), b = q(1, n);
    multiset<string> ss;
    for (string i : a)
        ss.insert(i);
    for (string i : b)
        ss.erase(ss.find(i));
    vector<string> d(n + 1);
    for (string i : ss)
        d[i.size()] = i;
    string ans(n, ' ');
    rep(i, n) {
        multiset<char> ms;
        for (char c : d[i + 1])
            ms.insert(c);
        for (char c : d[i])
            ms.erase(ms.find(c));
        ans[i] = *ms.begin();
    }
    return ans;
}

int main() {
#ifdef ONPC
    freopen("a.in", "r", stdin);
#endif
	int n;
    cin >> n;
    string ans = solve((n + 1) / 2) + string(n / 2, ' ');
    vector<string> aa = q(0, n);
    vector<multiset<string>> a(n + 1);
    for (string i : aa)
        a[i.size()].insert(i);
    multiset<char> ms, cs, fs;
    for (char c : *a[n].begin())
        ms.insert(c);
    fs = ms;
    for (int i = n - 1; i >= (n + 1) / 2; i--) {
        cs = ms;
        rep(j, (n + 1) / 2 + 1) {
            string ws;
            for (char c : cs)
                ws.push_back(c);
            a[ws.size()].erase(a[ws.size()].find(ws));
            if (j < (n + 1) / 2)
                cs.erase(cs.find(ans[j]));
        }
        cs = fs;
        for (char c : *a[i].begin())
            cs.erase(cs.find(c));
        ans[i] = *cs.begin();
        if (i > (n + 1) / 2)
            ms.erase(ms.find(ans[i]));
    }
    cout << "! " << ans;
    cout << endl;
}


#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<string>

const int inf = 1000000000;
using namespace std;

int main() {
	int n;
	cin >> n;
	multiset<string>ss;
	vector<string>a(n + 1);
	string s;
	if (n < 4) {
		char ch;
		for (int i = 1; i <= n; i += 1) {
			cout << "? " << i << " " << i << endl;
			cin >> ch;
			s += ch;
		}
		cout << "? " << s << endl;
	}
	int k = (n) / 2;
	int count = k * (k + 1) / 2;
	cout << "? 1 " << k << endl;
	while (count--) {
		cin >> s;
		sort(s.begin(), s.end());
		ss.insert(s);
	}
	count = k * (k - 1) / 2;
	multiset<string>ss2 = ss;
	cout << "? 2 " << k << endl;
	while (count--) {
		cin >> s;
		sort(s.begin(), s.end());
		ss2.erase(ss2.find(s));
	}
	string ans;
	for (string x : ss2) {
		a[x.length()] = x;
	}
	multiset<char>ss3;
	for (int i = 1; i <= k; i += 1) {
		ss3 = multiset<char>(a[i].begin(), a[i].end());
		for (char x : a[i - 1]) {
			ss3.erase(ss3.find(x));
		}
		ans += *ss3.begin();
	}
	cout << "? 1 " << n << endl;
	count = n * (n + 1) / 2;
	vector<int>dp(n + 1);
	while (count--) {
		cin >> s;
		int len = s.length();
		if (len <= k) {
			continue;
		}
		for (char x : s) {
			dp[len] += x - 'a';
		}
	}
	for (int i = k + 1; i <= n; i += 1) {
		for (int j = 1; j <= k; j += 1) {
			dp[i] -= min(j, n - i + 1) * (ans[j] - 'a');
		}
	}
	for (int i = k + 1; i <= n; i += 1) {
		dp[i] -= dp[i + 1];
	}
	for (int i = n; i > k; i -= 1) {
		dp[i] -= dp[i - 1];
		ans[i] = dp[i];
	}
	cout << "! " << ans << endl;
}


#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<string>

const int inf = 1000000000;
using namespace std;

int main() {
	int n;
	cin >> n;
	multiset<string>ss;
	vector<string>a(n + 1);
	string s;
	if (n < 4) {
		char ch;
		for (int i = 1; i <= n; i += 1) {
			cout << "? " << i << " " << i << endl;
			cin >> ch;
			s += ch;
		}
		cout << "! " << s << endl;
		return 0;
	}
	int k = (n) / 2;
	int count = k * (k + 1) / 2;
	cout << "? 1 " << k << endl;
	while (count--) {
		cin >> s;
		sort(s.begin(), s.end());
		ss.insert(s);
	}
	count = k * (k - 1) / 2;
	cout << "? 2 " << k << endl;
	while (count--) {
		cin >> s;
		sort(s.begin(), s.end());
		ss.erase(ss.find(s));
	}
	vector<char>ans(n + 5);
	for (string x : ss) {
		a[x.length()] = x;
	}
	multiset<char>ss3;
	for (int i = 1; i <= k; i += 1) {
		ss3 = multiset<char>(a[i].begin(), a[i].end());
		for (char x : a[i - 1]) {
			ss3.erase(ss3.find(x));
		}
		ans[i] = *ss3.begin();
	}
	cout << "? 1 " << n << endl;
	count = n * (n + 1) / 2;
	vector<int>dp(n + 5);
	while (count--) {
		cin >> s;
		int len = s.length();
		if (len <= k) {
			continue;
		}
		for (char x : s) {
			dp[len] += x - 'a';
		}
	}
	for (int i = k + 1; i <= n; i += 1) {
		for (int j = 1; j <= k; j += 1) {
			dp[i] -= min(j, n - i + 1) * (ans[j] - 'a');
		}
	}
	for (int i = k + 1; i <= n; i += 1) {
		dp[i] -= dp[i + 1];
	}
	for (int i = n; i > k; i -= 1) {
		dp[i] -= dp[i - 1];
		ans[i] = (char)(dp[i] + 'a');
	}
	cout << "! ";
	for (int i = 1; i <= n; i += 1) {
		cout << ans[i];
	}
	cout << endl;
}


#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
#include<string>

const int inf = 1000000000;
using namespace std;

int main() {
	int n;
	cin >> n;
	multiset<string>ss;
	vector<string>a(n + 1);
	string s;
	if (n < 4) {
		char ch;
		for (int i = 1; i <= n; i += 1) {
			cout << "? " << i << " " << i << endl;
			cin >> ch;
			s += ch;
		}
		cout << "? " << s << endl;
	}
	int k = (n) / 2;
	int count = k * (k + 1) / 2;
	cout << "? 1 " << k << endl;
	while (count--) {
		cin >> s;
		sort(s.begin(), s.end());
		ss.insert(s);
	}
	count = k * (k - 1) / 2;
	cout << "? 2 " << k << endl;
	while (count--) {
		cin >> s;
		sort(s.begin(), s.end());
		ss.erase(ss.find(s));
	}
	string ans;
	for (string x : ss) {
		a[x.length()] = x;
	}
	multiset<char>ss3;
	for (int i = 1; i <= k; i += 1) {
		ss3 = multiset<char>(a[i].begin(), a[i].end());
		for (char x : a[i - 1]) {
			ss3.erase(ss3.find(x));
		}
		ans += *ss3.begin();
	}
	cout << "? 1 " << n << endl;
	count = n * (n + 1) / 2;
	vector<int>dp(n + 1);
	while (count--) {
		cin >> s;
		int len = s.length();
		if (len <= k) {
			continue;
		}
		for (char x : s) {
			dp[len] += x - 'a';
		}
	}
	for (int i = k + 1; i <= n; i += 1) {
		for (int j = 1; j <= k; j += 1) {
			dp[i] -= min(j, n - i + 1) * (ans[j - 1] - 'a');
		}
	}
	for (int i = k + 1; i <= n; i += 1) {
		dp[i] -= dp[i + 1];
	}
	for (int i = n; i > k; i -= 1) {
		dp[i] -= dp[i - 1];
		ans[i - 1] = dp[i];
	}
	cout << "! " << ans << endl;
}


#include <iostream>
#include <iomanip>
#include <algorithm>
#include <vector>
#include <string>
#include <math.h>
#include <functional>
#include <set>

using namespace std;

typedef long long ll;

const int hsize = 'z' - 'a' + 1;

void add(vector<int>& hist, const string& s) {
	for (int i = 0; i < s.length(); i++)
		hist[s[i] - 'a']++;
}

vector<int> sub(const vector<int>& a, const vector<int>& b) {
	vector<int> res(hsize);
	for (int i = 0; i < hsize; i++){
		res[i] = a[i] - b[i];
	}
	return res;
}

vector<int> mul(const vector<int>& a, const int b) {
	vector<int> res(hsize);
	for (int i = 0; i < hsize; i++){
		res[i] = a[i] * b;
	}
	return res;
}

vector<string> ask(int l, int r) {
	cout << "? " << l << " " << r << endl;
	int n = r - l + 1;
	int cnt = n * (n + 1) / 2;
	vector<string> res(cnt);
	for (int i = 0; i < cnt; i++)
		cin >> res[i];
	return res;
}

string hist_to_str(const vector<int>& h){
	string res;
	for (int j = 0; j < hsize; j++)
	    if (h[j] > 0)
		res += string(h[j], 'a' + j);
	return res;
}

vector<string> substr_to_pairs(const vector<string> substr, int len) {
	vector<vector<int> > hists(len, vector<int>(hsize, 0));
	for (int i = 0; i < substr.size(); i++) {
		add(hists[substr[i].length() - 1], substr[i]);
	}
	vector<string> res;
	for (int i = 0; i < len / 2; i++) {
		vector<int> h = sub(mul(hists[0], i + 2), hists[len - i - 2]);
		for (int j = 0; j < res.size(); j++) {
			h[res[j][0] - 'a'] -= res.size() + 1 - j;
			h[res[j][1] - 'a'] -= res.size() + 1 - j;
		}
		res.push_back(hist_to_str(h));
	}
	if (len % 2 != 0) {
		vector<int> h = hists[0];
		for (int j = 0; j < res.size(); j++) {
			h[res[j][0] - 'a']--;
			h[res[j][1] - 'a']--;
		}
		res.push_back(hist_to_str(h));// one char
	}
	return res;
}

char get_next(const string& s, const char c) {
	return s[0] == c? s[1]: s[0];
}
int main() {
	ios::sync_with_stdio(false);
        int n;
	cin >> n;
	if (n <= 3) {
		string s;
		for (int i = 1; i <= n; i++) {
			s += ask(i, i)[0];
		}
		cout << "! " << s << endl;
		return 0;
	}
	int len1 = n / 2;
	int len2 = (n % 2 == 0)? len1 - 1: n - len1;
	auto rep1 = substr_to_pairs(ask(1, n), n);
	auto rep2 = substr_to_pairs(ask(1, len1), len1);
	auto rep3 = substr_to_pairs(ask(n - len2 + 1, n), len2);

	vector<vector<pair<string, int>> > edges(n);
	string s(n, ' ');

	auto addEdges = [&](const vector<string>& prs, int l, int r) {
		int i = 0;
		while (l < r) {
//			cout << "link " << l << " " << r << " -> " <<  prs[i] << endl;
			edges[l].push_back(make_pair(prs[i], r));
			edges[r].push_back(make_pair(prs[i], l));
			l++; r--; i++;
		}
		if (l == r)
			s[r] = prs[i][0]; // set midpoint (it is always known)
	};

	addEdges(rep1, 0, n - 1);
	addEdges(rep2, 0, len1 - 1);
	addEdges(rep3, n - len2, n - 1);

	function<void(int)> dfs;
	dfs = [&](int v) {
		for (int i = 0; i < edges[v].size(); i++) {
		        int np = edges[v][i].second;
			if (s[np] == ' ') {
				s[np] = get_next(edges[v][i].first, s[v]);
//				cout << "move from " << v << " to " << np << " pair = " << edges[v][i].first << " cur = " << s[v] << " set = " << s[np] << endl;
				dfs(np);
			}
		}
	};
	for (int i = 0; i < n; i++)
		if (s[i] != ' ')
			dfs(i);

	cout << "! " << s << endl;

	return 0;
}


#include <iostream>
#include <iomanip>
#include <algorithm>
#include <vector>
#include <string>
#include <math.h>
#include <functional>
#include <set>

using namespace std;

typedef long long ll;

const int hsize = 'z' - 'a' + 1;

void add(vector<int>& hist, const string& s) {
	for (int i = 0; i < s.length(); i++)
		hist[s[i] - 'a']++;
}

vector<int> sub(const vector<int>& a, const vector<int>& b) {
	vector<int> res(hsize);
	for (int i = 0; i < hsize; i++){
		res[i] = a[i] - b[i];
	}
	return res;
}

vector<int> mul(const vector<int>& a, const int b) {
	vector<int> res(hsize);
	for (int i = 0; i < hsize; i++){
		res[i] = a[i] * b;
	}
	return res;
}

vector<string> ask(int l, int r) {
	cout << "? " << l << " " << r << endl;
	int n = r - l + 1;
	int cnt = n * (n + 1) / 2;
	vector<string> res(cnt);
	for (int i = 0; i < cnt; i++)
		cin >> res[i];
	return res;
}

string hist_to_str(const vector<int>& h){
	string res;
	for (int j = 0; j < hsize; j++)
	    if (h[j] > 0)
		res += string(h[j], 'a' + j);
	return res;
}

vector<string> substr_to_pairs(const vector<string> substr, int len) {
	vector<vector<int> > hists(len, vector<int>(hsize, 0));
	for (int i = 0; i < substr.size(); i++) {
		add(hists[substr[i].length() - 1], substr[i]);
	}
	vector<string> res;
	for (int i = 0; i < len / 2; i++) {
		vector<int> h = sub(mul(hists[0], i + 2), hists[len - i - 2]);
		for (int j = 0; j < res.size(); j++) {
			h[res[j][0] - 'a'] -= res.size() + 1 - j;
			h[res[j][1] - 'a'] -= res.size() + 1 - j;
		}
		res.push_back(hist_to_str(h));
	}
	if (len % 2 != 0) {
		vector<int> h = hists[0];
		for (int j = 0; j < res.size(); j++) {
			h[res[j][0] - 'a']--;
			h[res[j][1] - 'a']--;
		}
		res.push_back(hist_to_str(h));// one char
	}
	return res;
}

char get_next(const string& s, const char c) {
	return s[0] == c? s[1]: s[0];
}
int main() {
	ios::sync_with_stdio(false);
        int n;
	cin >> n;
	if (n <= 3) {
		string s;
		for (int i = 1; i <= n; i++) {
			s += ask(i, i)[0];
		}
		cout << "! " << s << endl;
		return 0;
	}
	int len1 = n / 2;
	int len2 = (n % 2 == 0)? len1 - 1: n - len1;
	auto rep1 = substr_to_pairs(ask(1, n), n);
	auto rep2 = substr_to_pairs(ask(1, len1), len1);
	auto rep3 = substr_to_pairs(ask(n - len2 + 1, n), len2);

	vector<vector<pair<string, int>> > edges(n);
	string s(n, ' ');

	auto addEdges = [&](const vector<string>& prs, int l, int r) {
		int i = 0;
		while (l < r) {
			edges[l].push_back(make_pair(prs[i], r));
			edges[r].push_back(make_pair(prs[i], l));
			l++; r--; i++;
		}
		if (l == r)
			s[r] = prs[i][0]; // set midpoint (it is always known)
	};

	addEdges(rep1, 0, n - 1);
	addEdges(rep2, 0, len1 - 1);
	addEdges(rep3, n - len2, n - 1);

	function<void(int)> dfs;
	dfs = [&](int v) {
		for (int i = 0; i < edges[v].size(); i++) {
		        int np = edges[v][i].second;
			if (s[np] == ' ') {
				s[np] = get_next(edges[v][i].first, s[i]);
				dfs(np);
			}
		}
	};
	for (int i = 0; i < n; i++)
		if (s[i] != ' ')
			dfs(i);

	cout << "! " << s << endl;

	return 0;
}


#include<stdio.h>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
using pii = pair<int, int>;
using pll = pair<long long int, long long int>;
using pli = pair<long long int, int>;
using pil = pair<int, long long int>;
using piii = pair<pii, pii>;
int n;
string s[1000009];
char xx[1009];
int v[109][109][26];
int ban[109][109];
int vl[109];
int w[109][109][26];
int can[109][109];
int wl[109];
int sl;
int res[109];
int btmp[26];
int ctmp[26];
int dtmp[26];
bool equal(int x[26], int y[26]) {
	for (int i = 0; i < 26; i++) {
		if (x[i] - y[i])return false;
	}
	return true;
}
void test(int si, int ei) {
	int i, j, k;

	printf("? %d %d\n", si, ei);
	fflush(stdout);
	sl = (ei-si+1)*(ei-si+2)/2;
	for (i = 0; i < sl; i++) {
		scanf("%s", xx);
		s[i] = xx;
	}

	/*
	char tmp[99];
	int ssl = 0;
	sl = (ei - si + 1) * (ei - si + 2) / 2;
	for (i = si-1; i < ei; i++) {
		for (j = i + 1; j <= ei; j++) {
			for (k = 0; k < j - i; k++) {
				tmp[k] = xx[i + k];
			}
			tmp[j - i] = 0;
			s[ssl++] = tmp;
		}
	}
	random_shuffle(s, s + ssl);
	*/
}
void need_more_job() {
	int i, j, k;
	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
		for (j = 0; j <= n; j++) {
			ban[i][j] = 0;
			can[i][j] = 0;
		}
	}
	test(1, n / 2);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			v[k][vl[k]][s[i][j] - 'a']++;
		}
		vl[k]++;
	}
	test(1, n / 2 - 1);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			w[k][wl[k]][s[i][j] - 'a']++;
		}
		wl[k]++;
	}
	for (int len = n / 2 - 1; len >= 1; len--) {
		for (i = 0; i < wl[len]; i++) {
			for (j = 0; j < vl[len]; j++) {
				if (ban[len][j] == 0 && equal(w[len][i], v[len][j])) {
					ban[len][j] = 1;
					break;
				}
			}
		}
	}
	for (i = 0; i < 26; i++) {
		btmp[i] = 0;
	}
	for (int len = 1; len <= n / 2; len++) {
		for (i = 0; i < vl[len]; i++) {
			if (ban[len][i] == 0) {
				for (j = 0; j < 26; j++) {
					if (btmp[j] != v[len][i][j])
						break;
				}
				int ri = n / 2 - len;
				if (res[ri] != j) {
					k = res[ri];
					res[ri] = res[n - ri - 1];
					res[n - ri - 1] = k;
				}
				btmp[j]++;
				break;
			}
		}
	}
}

void need_more_job_2() {
	int i, j, k;
	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
		for (j = 0; j <= n; j++) {
			ban[i][j] = 0;
			can[i][j] = 0;
		}
	}
	test(1, n / 2);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			v[k][vl[k]][s[i][j] - 'a']++;
		}
		vl[k]++;
	}
	test(1, n / 2 - 1);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			w[k][wl[k]][s[i][j] - 'a']++;
		}
		wl[k]++;
	}
	for (int len = n / 2 - 1; len >= 1; len--) {
		for (i = 0; i < wl[len]; i++) {
			for (j = 0; j < vl[len]; j++) {
				if (ban[len][j] == 0 && equal(w[len][i], v[len][j])) {
					ban[len][j] = 1;
					break;
				}
			}
		}
	}
	for (i = 0; i < 26; i++) {
		btmp[i] = 0;
	}
	for (int len = 1; len <= n / 2; len++) {
		for (i = 0; i < vl[len]; i++) {
			if (ban[len][i] == 0) {
				for (j = 0; j < 26; j++) {
					if (btmp[j] != v[len][i][j])
						break;
				}
				int ri = n / 2 - len;
				res[ri] = j;
				btmp[j]++;
				break;
			}
		}
	}

	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
			ban[i][j] = 0;
		}
		for (j = 0; j < wl[i]; j++) {
			for (k = 0; k < 26; k++) {
				w[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
	}
}
int main() {
	int i, j, k, l;
	int t = 1, tv = 0;
	while (t--) {
		scanf("%d", &n);

		if (n <= 3) {
			for (i = 0; i < n; i++) {
				test(i + 1, i + 1);
				res[i] = s[0][0]-'a';
			}
			printf("! ");
			for (i = 0; i < n; i++)printf("%c", res[i] + 'a');
			printf("\n");
			fflush(stdout);
			continue;
		}
		else {
			need_more_job_2();
		}
		test(1, n);
		for (i = 0; i < sl; i++) {
			k = s[i].size();
			for (j = 0; j < k; j++) {
				v[k][vl[k]][s[i][j] - 'a']++;
			}
			vl[k]++;
		}
		for (i = 1; i < n; i++) {
			for (j = 0; j < vl[i]; j++) {
				for (k = 0; k < 26; k++) {
					v[i][j][k] = v[n][0][k] - v[i][j][k];
				}
			}
		}
		for (int len = n - 1; len >= n / 2; len--) {
			for (j = 0; j < 26; j++) {
				btmp[j] = 0;
			}
			for (i = 0; i < vl[len]; i++) {
				for (j = 0; j < 26; j++) {
					btmp[j] += v[len][i][j];
				}
			}
			// delete half
			for (i = 0; i < n - len; i++) {
				btmp[res[i]] -= (n - len - i);
			}
			//delete back
			for (i = n - 1; i > len; i--) {
				btmp[res[i]] -= (i - len + 1);
			}
			for (j = 0; j < 26; j++) {
				if (btmp[j]) {
					res[len] = j;
					break;
				}
			}
		}

		printf("! ");
		for (i = 0; i < n; i++) {
			printf("%c", res[i] + 'a');
		}
		printf("\n");
		fflush(stdout);
	}
}


#include<stdio.h>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
using pii = pair<int, int>;
using pll = pair<long long int, long long int>;
using pli = pair<long long int, int>;
using pil = pair<int, long long int>;
using piii = pair<pii, pii>;
int n;
string s[1000009];
char xx[1009];
int v[109][109][26];
int ban[109][109];
int vl[109];
int w[109][109][26];
int can[109][109];
int wl[109];
int sl;
int res[109];
int btmp[26];
int ctmp[26];
int dtmp[26];
bool equal(int x[26], int y[26]) {
	for (int i = 0; i < 26; i++) {
		if (x[i] - y[i])return false;
	}
	return true;
}
void test(int si, int ei) {
	int i, j, k;

	printf("? %d %d\n", si, ei);
	fflush(stdout);
	sl = (ei-si+1)*(ei-si+2)/2;
	for (i = 0; i < sl; i++) {
		scanf("%s", xx);
		s[i] = xx;
	}

	/*
	char tmp[99];
	int ssl = 0;
	sl = (ei - si + 1) * (ei - si + 2) / 2;
	for (i = si-1; i < ei; i++) {
		for (j = i + 1; j <= ei; j++) {
			for (k = 0; k < j - i; k++) {
				tmp[k] = xx[i + k];
			}
			tmp[j - i] = 0;
			s[ssl++] = tmp;
		}
	}
	random_shuffle(s, s + ssl);
	*/
}
void need_more_job() {
	int i, j, k;
	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
		for (j = 0; j <= n; j++) {
			ban[i][j] = 0;
			can[i][j] = 0;
		}
	}
	test(1, n / 2);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			v[k][vl[k]][s[i][j] - 'a']++;
		}
		vl[k]++;
	}
	test(1, n / 2 - 1);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			w[k][wl[k]][s[i][j] - 'a']++;
		}
		wl[k]++;
	}
	for (int len = n / 2 - 1; len >= 1; len--) {
		for (i = 0; i < wl[len]; i++) {
			for (j = 0; j < vl[len]; j++) {
				if (ban[len][j] == 0 && equal(w[len][i], v[len][j])) {
					ban[len][j] = 1;
					break;
				}
			}
		}
	}
	for (i = 0; i < 26; i++) {
		btmp[i] = 0;
	}
	for (int len = 1; len <= n / 2; len++) {
		for (i = 0; i < vl[len]; i++) {
			if (ban[len][i] == 0) {
				for (j = 0; j < 26; j++) {
					if (btmp[j] != v[len][i][j])
						break;
				}
				int ri = n / 2 - len;
				if (res[ri] != j) {
					k = res[ri];
					res[ri] = res[n - ri - 1];
					res[n - ri - 1] = k;
				}
				btmp[j]++;
				break;
			}
		}
	}
}

void need_more_job_2() {
	int i, j, k;
	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
		for (j = 0; j <= n; j++) {
			ban[i][j] = 0;
			can[i][j] = 0;
		}
	}
	test(1, (n + 1) / 2);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			v[k][vl[k]][s[i][j] - 'a']++;
		}
		vl[k]++;
	}
	test(1, (n + 1) / 2 - 1);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			w[k][wl[k]][s[i][j] - 'a']++;
		}
		wl[k]++;
	}
	for (int len = (n + 1) / 2 - 1; len >= 1; len--) {
		for (i = 0; i < wl[len]; i++) {
			for (j = 0; j < vl[len]; j++) {
				if (ban[len][j] == 0 && equal(w[len][i], v[len][j])) {
					ban[len][j] = 1;
					break;
				}
			}
		}
	}
	for (i = 0; i < 26; i++) {
		btmp[i] = 0;
	}
	for (int len = 1; len <= (n + 1) / 2; len++) {
		for (i = 0; i < vl[len]; i++) {
			if (ban[len][i] == 0) {
				for (j = 0; j < 26; j++) {
					if (btmp[j] != v[len][i][j])
						break;
				}
				int ri = (n + 1) / 2 - len;
				res[ri] = j;
				btmp[j]++;
				break;
			}
		}
	}

	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
			ban[i][j] = 0;
		}
		for (j = 0; j < wl[i]; j++) {
			for (k = 0; k < 26; k++) {
				w[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
	}
}
int main() {
	int i, j, k, l;
	int t = 1, tv = 0;
	while (t--) {
		scanf("%d", &n);
		//scanf("%s", xx);

		if (n <= 3) {
			for (i = 0; i < n; i++) {
				test(i + 1, i + 1);
				res[i] = s[0][0]-'a';
			}
			printf("! ");
			for (i = 0; i < n; i++)printf("%c", res[i] + 'a');
			printf("\n");
			fflush(stdout);
			continue;
		}
		else {
			need_more_job_2();
		}
		test(1, n);
		for (i = 0; i < sl; i++) {
			k = s[i].size();
			for (j = 0; j < k; j++) {
				v[k][vl[k]][s[i][j] - 'a']++;
			}
			vl[k]++;
		}
		for (i = 1; i < n; i++) {
			for (j = 0; j < vl[i]; j++) {
				for (k = 0; k < 26; k++) {
					v[i][j][k] = v[n][0][k] - v[i][j][k];
				}
			}
		}
		for (int len = n - 1; len >= (n+1) / 2; len--) {
			for (j = 0; j < 26; j++) {
				btmp[j] = 0;
			}
			for (i = 0; i < vl[len]; i++) {
				for (j = 0; j < 26; j++) {
					btmp[j] += v[len][i][j];
				}
			}
			// delete half
			for (i = 0; i < n - len; i++) {
				btmp[res[i]] -= (n - len - i);
			}
			//delete back
			for (i = n - 1; i > len; i--) {
				btmp[res[i]] -= (i - len + 1);
			}
			for (j = 0; j < 26; j++) {
				if (btmp[j]) {
					res[len] = j;
					break;
				}
			}
		}

		printf("! ");
		for (i = 0; i < n; i++) {
			printf("%c", res[i] + 'a');
		}
		printf("\n");
		fflush(stdout);
	}
}


#include<stdio.h>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
using pii = pair<int, int>;
using pll = pair<long long int, long long int>;
using pli = pair<long long int, int>;
using pil = pair<int, long long int>;
using piii = pair<pii, pii>;
int n;
string s[1000009];
char xx[1009];
int v[109][109][26];
int ban[109][109];
int vl[109];
int w[109][109][26];
int can[109][109];
int wl[109];
int sl;
int res[109];
int btmp[26];
int ctmp[26];
int dtmp[26];
bool equal(int x[26], int y[26]) {
	for (int i = 0; i < 26; i++) {
		if (x[i] - y[i])return false;
	}
	return true;
}
void test(int si, int ei) {
	int i, j, k;

	printf("? %d %d\n", si, ei);
	fflush(stdout);
	sl = (ei-si+1)*(ei-si+2)/2;
	for (i = 0; i < sl; i++) {
		scanf("%s", xx);
		s[i] = xx;
	}

	/*
	char tmp[99];
	int ssl = 0;
	sl = (ei - si + 1) * (ei - si + 2) / 2;
	for (i = si-1; i < ei; i++) {
		for (j = i + 1; j <= ei; j++) {
			for (k = 0; k < j - i; k++) {
				tmp[k] = xx[i + k];
			}
			tmp[j - i] = 0;
			s[ssl++] = tmp;
		}
	}
	random_shuffle(s, s + ssl);
	*/
}
void need_more_job() {
	int i, j, k;
	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
		for (j = 0; j <= n; j++) {
			ban[i][j] = 0;
			can[i][j] = 0;
		}
	}
	test(1, n / 2);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			v[k][vl[k]][s[i][j] - 'a']++;
		}
		vl[k]++;
	}
	test(1, n / 2 - 1);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			w[k][wl[k]][s[i][j] - 'a']++;
		}
		wl[k]++;
	}
	for (int len = n / 2 - 1; len >= 1; len--) {
		for (i = 0; i < wl[len]; i++) {
			for (j = 0; j < vl[len]; j++) {
				if (ban[len][j] == 0 && equal(w[len][i], v[len][j])) {
					ban[len][j] = 1;
					break;
				}
			}
		}
	}
	for (i = 0; i < 26; i++) {
		btmp[i] = 0;
	}
	for (int len = 1; len <= n / 2; len++) {
		for (i = 0; i < vl[len]; i++) {
			if (ban[len][i] == 0) {
				for (j = 0; j < 26; j++) {
					if (btmp[j] != v[len][i][j])
						break;
				}
				int ri = n / 2 - len;
				if (res[ri] != j) {
					k = res[ri];
					res[ri] = res[n - ri - 1];
					res[n - ri - 1] = k;
				}
				btmp[j]++;
				break;
			}
		}
	}
}

void need_more_job_2() {
	int i, j, k;
	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
		for (j = 0; j <= n; j++) {
			ban[i][j] = 0;
			can[i][j] = 0;
		}
	}
	test(1, n / 2 + 1);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			v[k][vl[k]][s[i][j] - 'a']++;
		}
		vl[k]++;
	}
	test(1, n / 2);
	for (i = 0; i < sl; i++) {
		k = s[i].size();
		for (j = 0; j < k; j++) {
			w[k][wl[k]][s[i][j] - 'a']++;
		}
		wl[k]++;
	}
	for (int len = n / 2; len >= 1; len--) {
		for (i = 0; i < wl[len]; i++) {
			for (j = 0; j < vl[len]; j++) {
				if (ban[len][j] == 0 && equal(w[len][i], v[len][j])) {
					ban[len][j] = 1;
					break;
				}
			}
		}
	}
	for (i = 0; i < 26; i++) {
		btmp[i] = 0;
	}
	for (int len = 1; len <= n / 2 + 1; len++) {
		for (i = 0; i < vl[len]; i++) {
			if (ban[len][i] == 0) {
				for (j = 0; j < 26; j++) {
					if (btmp[j] != v[len][i][j])
						break;
				}
				int ri = n / 2 - len;
				res[ri] = j;
				btmp[j]++;
				break;
			}
		}
	}

	for (i = 0; i <= n; i++) {
		for (j = 0; j < vl[i]; j++) {
			for (k = 0; k < 26; k++) {
				v[i][j][k] = 0;
			}
			ban[i][j] = 0;
		}
		for (j = 0; j < wl[i]; j++) {
			for (k = 0; k < 26; k++) {
				w[i][j][k] = 0;
			}
		}
		vl[i] = 0;
		wl[i] = 0;
	}
}
int main() {
	int i, j, k, l;
	int t = 1, tv = 0;
	while (t--) {
		scanf("%d", &n);

		if (n <= 3) {
			for (i = 0; i < n; i++) {
				test(i + 1, i + 1);
				res[i] = s[0][0]-'a';
			}
			printf("! ");
			for (i = 0; i < n; i++)printf("%c", res[i] + 'a');
			printf("\n");
			fflush(stdout);
			continue;
		}
		else {
			need_more_job_2();
		}
		test(1, n);
		for (i = 0; i < sl; i++) {
			k = s[i].size();
			for (j = 0; j < k; j++) {
				v[k][vl[k]][s[i][j] - 'a']++;
			}
			vl[k]++;
		}
		for (i = 1; i < n; i++) {
			for (j = 0; j < vl[i]; j++) {
				for (k = 0; k < 26; k++) {
					v[i][j][k] = v[n][0][k] - v[i][j][k];
				}
			}
		}
		for (int len = n - 1; len > n / 2; len--) {
			for (j = 0; j < 26; j++) {
				btmp[j] = 0;
			}
			for (i = 0; i < vl[len]; i++) {
				for (j = 0; j < 26; j++) {
					btmp[j] += v[len][i][j];
				}
			}
			// delete half
			for (i = 0; i < n - len; i++) {
				btmp[res[i]] -= (n - len - i);
			}
			//delete back
			for (i = n - 1; i > len; i--) {
				btmp[res[i]] -= (i - len + 1);
			}
			for (j = 0; j < 26; j++) {
				if (btmp[j]) {
					res[len] = j;
					break;
				}
			}
		}

		printf("! ");
		for (i = 0; i < n; i++) {
			printf("%c", res[i] + 'a');
		}
		printf("\n");
		fflush(stdout);
	}
}


#include<stdio.h>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
using pii = pair<int, int>;
using pll = pair<long long int, long long int>;
using pli = pair<long long int, int>;
using pil = pair<int, long long int>;
using piii = pair<pii, pii>;
int n;
string s[1000009];
char xx[1009];
int v[109][109][26];
int ban[109][109];
int vl[109];
int sl;
int res[109];
int btmp[26];
int ctmp[26];
int dtmp[26];
bool equal(int x[26], int y[26]) {
	for (int i = 0; i < 26; i++) {
		if (x[i] - y[i])return false;
	}
	return true;
}
void test() {
	int i, j, k;

	printf("? 1 %d\n", n);
	fflush(stdout);
	for (i = 0; i < sl; i++) {
		scanf("%s", xx);
		s[i] = xx;
	}
	/*
	scanf("%s", xx);
	char tmp[99];
	int ssl = 0;
	for (i = 0; i < n; i++) {
		for (j = i + 1; j <= n; j++) {
			for (k = 0; k < j - i; k++) {
				tmp[k] = xx[i + k];
			}
			tmp[j - i] = 0;
			s[ssl++] = tmp;
		}
	}
	*/
}
int main() {
	int i, j, k, l;
	int t = 1, tv = 0;
	while (t--) {
		scanf("%d", &n);
		sl = n * (n + 1) / 2;
		test();
		for (i = 0; i < sl; i++) {
			k = s[i].size();
			for (j = 0; j < k; j++) {
				v[k][vl[k]][s[i][j]-'a']++;
			}
			vl[k]++;
		}
		if (n == 1) {
			printf("! %s\n", s[0].c_str());
			fflush(stdout);
			continue;
		}
		for (i = 1; i < n; i++) {
			for (j = 0; j < vl[i]; j++) {
				for (k = 0; k < 26; k++) {
					v[i][j][k] = v[n][0][k] - v[i][j][k];
				}
			}
		}
		for (i = 0; i < vl[n - 1]; i++) {
			for (j = 0; j < 26; j++) {
				if (v[n-1][i][j] > 0)
					break;
			}
			res[i * (n - 1)] = j;
		}
		for (int len = n - 2; len >= n / 2; len--) {
			//make ban list
			for (j = 0; j < 26; j++) {
				btmp[j] = 0;
				ctmp[j] = 0;
				dtmp[j] = 0;
			}
			for (j = 0; j < n - len - 1; j++) {
				ctmp[res[j]]++;
				dtmp[res[n - j - 1]]++;
			}
			for (j = 0; j < n - len; j++) {
				btmp[res[j]]++;
			}
			k = n - 1;
			j = n - len - 1;
			while (j > 0) {
				btmp[res[j]]--;
				btmp[res[k]]++;
				j--;
				k--;
				for (i = 0; i < vl[len]; i++) {
					if (ban[len][i] == 0 && equal(btmp, v[len][i]))
					{
						ban[len][i] = 1;
						break;
					}
				}
			}
			bool check_first = false;
			bool check_last = false;
			for (i = 0; i < vl[len]; i++) {
				if (ban[len][i] == 0) {
					int dif1, dif2;
					int di1, di2;
					dif1 = 0;
					dif2 = 0;
					di1 = -1;
					di2 = -1;
					//check with ctmp
					for (k = 0; k < 26; k++) {
						if (ctmp[k] != v[len][i][k]) {
							dif1++;
							di1 = k;
						}
						if (dtmp[k] != v[len][i][k]) {
							dif2++;
							di2 = k;
						}
					}
					if (dif1 != 1) {
						check_last = true;
						res[len] = di2;
						ban[len][i] = 1;
					}
					else if(dif2 != 1){
						check_first = true;
						res[n - len - 1] = di1;
						ban[len][i] = 1;
					}
				}
			}
			if (!check_first || !check_last) {
				for (i = 0; i < vl[len]; i++) {
					if (ban[len][i] == 0) {
						int dif1, dif2;
						int di1, di2;
						dif1 = 0;
						dif2 = 0;
						di1 = -1;
						di2 = -1;
						//check with ctmp
						for (k = 0; k < 26; k++) {
							if (ctmp[k] != v[len][i][k]) {
								dif1++;
								di1 = k;
							}
							if (dtmp[k] != v[len][i][k]) {
								dif2++;
								di2 = k;
							}
						}

						if (check_first || dif1 != 1) {
							check_last = true;
							res[len] = di2;
							ban[len][i] = 1;
						}
						else {
							check_first = true;
							res[n - len - 1] = di1;
							ban[len][i] = 1;
						}
					}
				}
			}
		}

		for (i = 0; i < n / 2; i++) {
			if (res[i] != res[n - i - 1]) {
				printf("? %d %d\n", i + 1, i + 1);
				fflush(stdout);
				scanf("%s", xx);
				if (xx[0] - 'a' != res[i])
				{
					for (i = 0; i < n / 2; i++) {
						j = res[i];
						res[i] = res[n - i - 1];
						res[n - i - 1] = j;
					}
				}
				break;
			}
		}
		printf("! ");
		for (i = 0; i < n; i++) {
			printf("%c", res[i]+'a');
		}
		printf("\n");
	}
}


/* https://codeforces.com/blog/entry/72867 */
#include <stdio.h>
#include <string.h>

#define N	100
#define A	26

int main() {
	static char cc[N + 1], ans[N + 1];
	static int kk[N + 1][A];
	int n, n_, m, h, i, j, l, a;

	scanf("%d", &n), n_ = n / 2;
	if (n > 1) {
		printf("? 1 %d\n", n_), fflush(stdout);
		m = n_ * (n_ + 1) / 2;
		for (h = 0; h < m; h++) {
			scanf("%s", cc), l = strlen(cc);
			for (i = 0; i < l; i++)
				ans[n_ + 1 - l] ^= cc[i];
		}
	}
	printf("? 1 %d\n", n_ + 1), fflush(stdout);
	m = (n_ + 1) * (n_ + 2) / 2;
	for (h = 0; h < m; h++) {
		scanf("%s", cc), l = strlen(cc);
		for (i = 0; i < l; i++)
			ans[n_ + 1 - l] ^= cc[i];
		for (i = 0; i < l; i++)
			kk[l][cc[i] - 'a']--;
	}
	for (i = 0; i < n_; i++)
		ans[i] ^= ans[i + 1];
	printf("? 1 %d\n", n), fflush(stdout);
	m = n * (n + 1) / 2;
	for (h = 0; h < m; h++) {
		scanf("%s", cc), l = strlen(cc);
		for (i = 0; i < l; i++)
			kk[l][cc[i] - 'a']++;
	}
	for (i = n - 1; i > n_; i--) {
		l = n + 1 - i;
		for (j = n_ + 2 - l; j <= n_; j++)
			kk[l][ans[j] - 'a']--;
		for (j = i + 1; j < n; j++)
			kk[l][ans[j] - 'a'] -= n - j;
		for (a = 0; a < A; a++)
			if (kk[l][a] % l != 0) {
				ans[i] = a + 'a';
				break;
			}
	}
	printf("! %s\n", ans);
	return 0;
}


/* https://codeforces.com/blog/entry/72867 */
#include <stdio.h>
#include <string.h>

#define N	100
#define A	26

int main() {
	static char cc[N + 1], ans[N + 1];
	static int kk[N + 1][A];
	int n, n_, m, h, i, j, l, a;

	scanf("%d", &n), n_ = n / 2;
	printf("? 1 %d\n", n_), fflush(stdout);
	m = n_ * (n_ + 1) / 2;
	for (h = 0; h < m; h++) {
		scanf("%s", cc), l = strlen(cc);
		for (i = 0; i < l; i++)
			ans[n_ + 1 - l] ^= cc[i];
	}
	printf("? 1 %d\n", n_ + 1), fflush(stdout);
	m = (n_ + 1) * (n_ + 2) / 2;
	for (h = 0; h < m; h++) {
		scanf("%s", cc), l = strlen(cc);
		for (i = 0; i < l; i++)
			ans[n_ + 1 - l] ^= cc[i];
		for (i = 0; i < l; i++)
			kk[l][cc[i] - 'a']--;
	}
	for (i = 0; i < n_; i++)
		ans[i] ^= ans[i + 1];
	printf("? 1 %d\n", n), fflush(stdout);
	m = n * (n + 1) / 2;
	for (h = 0; h < m; h++) {
		scanf("%s", cc), l = strlen(cc);
		for (i = 0; i < l; i++)
			kk[l][cc[i] - 'a']++;
	}
	for (i = n - 1; i > n_; i--) {
		l = n + 1 - i;
		for (j = n_ + 2 - l; j <= n_; j++)
			kk[l][ans[j] - 'a']--;
		for (j = i + 1; j < n; j++)
			kk[l][ans[j] - 'a'] -= n - j;
		for (a = 0; a < A; a++)
			if (kk[l][a] % l != 0) {
				ans[i] = a + 'a';
				break;
			}
	}
	printf("! %s\n", ans);
	return 0;
}


from sys import stdout

class String2:
    def __init__(self, s, b=None):
        if b is not None:
            self.b = b
            return
        self.b = [0] * 26
        for i in s:
            self.b[ord(i) - ord('a')] += 1

    def __add__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] += other.b[i]
        return String2('', b)

    def __sub__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] -= other.b[i]
        return b

    def __mul__(self, other):
        ans = String2('', self.b)
        for i in range(26):
            ans.b[i] *= other
        return ans

def req(l, r, k=0):
    print('?', l, r)
    v = [''.join(sorted(input())) for i in range((r - l + 1) * (r - l + 2) // 2)]
    stdout.flush()
    return v

def compute(v):
    bukvi = [[0] * (n + 2) for _ in range(26)]
    for el in v:
        cur = len(el)
        for e in el:
            bukvi[ord(e) - ord('a')][cur] += 1
    return bukvi

def compute2(bukvi):
    bukvis = [set() for i in range(n + 2)]
    for i in range(26):
        prev = bukvi[i][1]
        for j in range(1, n // 2 + n % 2 + 1):
            while bukvi[i][j] != prev:
                bukvis[j].add(chr(ord('a') + i))
                prev += 1
    return bukvis

def solve(va, va2):
    for i in va2:
        va.remove(i)
    va.sort(key=len)
    s = va[0]
    for i in range(1, len(va)):
        for j in range(26):
            if va[i].count(chr(ord('a') + j)) != va[i - 1].count(chr(ord('a') + j)):
                s += chr(ord('a') + j)
    return s

def check(v, s, s2, f):
    s3 = String2(v[0])
    for i in range(1, len(v)):
        s3 = s3 + String2(v[i])
    le = len(v[0])
    cur = String2('', String2('', f - String2(s)) - String2(s2)) * le
    for i in range(le - 2):
        cur = cur + (String2(s[i]) * (i + 1)) + (String2(s2[-i]) * (i + 1))
    cur = cur + (String2(s[le - 2]) * (le - 1)) + (String2(s[le - 1]) * le)
    e = cur - s3
    for i in range(26):
        if e[i]:
            return chr(ord('a') + i)

def main():
    if n == 1:
        va = req(1, 1)
        print('!', va[0])
        return
    elif n == 2:
        va2 = req(1, 1)
        va3 = req(2, 2)
        print('!', va2[0] + va3[0])
        return
    elif n == 3:
        va = req(1, 1)
        va2 = req(2, 2)
        va3 = req(3, 3)
        print('!', va[0] + va2[0] + va3[0])
        return
    va = req(1, n)
    va2 = req(1, max(n // 2 + n % 2, 2))
    va3 = req(2, max(n // 2 + n % 2, 2))
    # bukvi2 = compute(va2)
    # bukvi3 = compute(va3)
    ma = [[] for i in range(n * 2)]
    for i in va:
        ma[len(i)].append(i)
    a = String2(''.join(ma[1]))
    s = solve(va2, va3)
    s2 = ''
    for i in range(2, n // 2 + 1):
        s2 = check(ma[i], s, s2, a) + s2
    se = String2('', a - String2(s)) - String2(s2)
    for i in range(len(se)):
        if se[i]:
            s += chr(ord('a') + i)
            break
    print('!', s + s2)

n = int(input())
main()


from sys import stdout

class String2:
    def __init__(self, s, b=None):
        if b is not None:
            self.b = b
            return
        self.b = [0] * 26
        for i in s:
            self.b[ord(i) - ord('a')] += 1

    def __add__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] += other.b[i]
        return String2('', b)

    def __sub__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] -= other.b[i]
        return b

    def __mul__(self, other):
        ans = String2('', self.b)
        for i in range(26):
            ans.b[i] *= other
        return ans

def req(l, r, k=0):
    print('?', l, r)
    v = [''.join(sorted(input())) for i in range((r - l + 1) * (r - l + 2) // 2)]
    stdout.flush()
    return v

def compute(v):
    bukvi = [[0] * (n + 2) for _ in range(26)]
    for el in v:
        cur = len(el)
        for e in el:
            bukvi[ord(e) - ord('a')][cur] += 1
    return bukvi

def compute2(bukvi):
    bukvis = [set() for i in range(n + 2)]
    for i in range(26):
        prev = bukvi[i][1]
        for j in range(1, n // 2 + n % 2 + 1):
            while bukvi[i][j] != prev:
                bukvis[j].add(chr(ord('a') + i))
                prev += 1
    return bukvis

def solve(va, va2):
    for i in va2:
        va.remove(i)
    va.sort(key=len)
    s = va[0]
    for i in range(1, len(va)):
        for j in range(26):
            if va[i].count(chr(ord('a') + j)) != va[i - 1].count(chr(ord('a') + j)):
                s += chr(ord('a') + j)
    return s

def check(v, s, s2, f):
    s3 = String2(v[0])
    for i in range(1, len(v)):
        s3 = s3 + String2(v[i])
    le = len(v[0])
    cur = String2('', String2('', f - String2(s)) - String2(s2)) * le
    for i in range(le - 2):
        cur = cur + (String2(s[i]) * (i + 1)) + (String2(s2[-i]) * (i + 1))
    cur = cur + (String2(s[le - 2]) * (le - 1)) + (String2(s[le - 1]) * le)
    e = cur - s3
    for i in range(26):
        if e[i]:
            return chr(ord('a') + i)

def main():
    va = req(1, n)
    va2 = req(1, n // 2 + n % 2)
    va3 = req(2, n // 2 + n % 2)
    # bukvi2 = compute(va2)
    # bukvi3 = compute(va3)
    if n == 1:
        print('!', va[0][0])
        return
    ma = [[] for i in range(n * 2)]
    for i in va:
        ma[len(i)].append(i)
    a = String2(''.join(ma[1]))
    s = solve(va2, va3)
    s2 = ''
    for i in range(2, n // 2 + 1):
        s2 = check(ma[i], s, s2, a) + s2
    se = String2('', a - String2(s)) - String2(s2)
    for i in range(len(se)):
        if se[i]:
            s += chr(ord('a') + i)
            break
    print(s + s2)

n = int(input())
main()


from sys import stdout

class String2:
    def __init__(self, s, b=None):
        if b is not None:
            self.b = b
            return
        self.b = [0] * 26
        for i in s:
            self.b[ord(i) - ord('a')] += 1

    def __add__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] += other.b[i]
        return String2('', b)

    def __sub__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] -= other.b[i]
        return b

    def __mul__(self, other):
        ans = String2('', self.b)
        for i in range(26):
            ans.b[i] *= other
        return ans

def req(l, r, k=0):
    print('?', l, r)
    v = [''.join(sorted(input())) for i in range((r - l + 1) * (r - l + 2) // 2)]
    stdout.flush()
    return v

def compute(v):
    bukvi = [[0] * (n + 2) for _ in range(26)]
    for el in v:
        cur = len(el)
        for e in el:
            bukvi[ord(e) - ord('a')][cur] += 1
    return bukvi

def compute2(bukvi):
    bukvis = [set() for i in range(n + 2)]
    for i in range(26):
        prev = bukvi[i][1]
        for j in range(1, n // 2 + n % 2 + 1):
            while bukvi[i][j] != prev:
                bukvis[j].add(chr(ord('a') + i))
                prev += 1
    return bukvis

def solve(va, va2):
    for i in va2:
        va.remove(i)
    va.sort(key=len)
    s = va[0]
    for i in range(1, len(va)):
        for j in range(26):
            if va[i].count(chr(ord('a') + j)) != va[i - 1].count(chr(ord('a') + j)):
                s += chr(ord('a') + j)
    return s

def check(v, s, s2, f):
    s3 = String2(v[0])
    for i in range(1, len(v)):
        s3 = s3 + String2(v[i])
    le = len(v[0])
    cur = String2('', String2('', f - String2(s)) - String2(s2)) * le
    for i in range(le - 2):
        cur = cur + (String2(s[i]) * (i + 1)) + (String2(s2[-i]) * (i + 1))
    cur = cur + (String2(s[le - 2]) * (le - 1)) + (String2(s[le - 1]) * le)
    e = cur - s3
    for i in range(26):
        if e[i]:
            return chr(ord('a') + i)

def main():
    if n == 1:
        print('!', va[0][0])
        return
    elif n == 2:
        va2 = req(1, 1)
        va3 = req(2, 2)
        print('!', va2[0] + va3[0])
        return
    elif n == 3:
        va = req(1, 1)
        va2 = req(2, 2)
        va3 = req(3, 3)
        print('!', va[0] + va2[0] + va3[0])
        return
    va = req(1, n)
    va2 = req(1, max(n // 2 + n % 2, 2))
    va3 = req(2, max(n // 2 + n % 2, 2))
    # bukvi2 = compute(va2)
    # bukvi3 = compute(va3)
    ma = [[] for i in range(n * 2)]
    for i in va:
        ma[len(i)].append(i)
    a = String2(''.join(ma[1]))
    s = solve(va2, va3)
    s2 = ''
    for i in range(2, n // 2 + 1):
        s2 = check(ma[i], s, s2, a) + s2
    se = String2('', a - String2(s)) - String2(s2)
    for i in range(len(se)):
        if se[i]:
            s += chr(ord('a') + i)
            break
    print('!', s + s2)

n = int(input())
main()


from sys import stdout

class String2:
    def __init__(self, s, b=None):
        if b is not None:
            self.b = b
            return
        self.b = [0] * 26
        for i in s:
            self.b[ord(i) - ord('a')] += 1

    def __add__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] += other.b[i]
        return String2('', b)

    def __sub__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] -= other.b[i]
        return b

    def __mul__(self, other):
        ans = String2('', self.b)
        for i in range(26):
            ans.b[i] *= other
        return ans

def req(l, r, k=0):
    print('?', l, r)
    v = [''.join(sorted(input())) for i in range((r - l + 1) * (r - l + 2) // 2)]
    stdout.flush()
    return v

def compute(v):
    bukvi = [[0] * (n + 2) for _ in range(26)]
    for el in v:
        cur = len(el)
        for e in el:
            bukvi[ord(e) - ord('a')][cur] += 1
    return bukvi

def compute2(bukvi):
    bukvis = [set() for i in range(n + 2)]
    for i in range(26):
        prev = bukvi[i][1]
        for j in range(1, n // 2 + n % 2 + 1):
            while bukvi[i][j] != prev:
                bukvis[j].add(chr(ord('a') + i))
                prev += 1
    return bukvis

def solve(va, va2):
    for i in va2:
        va.remove(i)
    va.sort(key=len)
    s = va[0]
    for i in range(1, len(va)):
        for j in range(26):
            if va[i].count(chr(ord('a') + j)) != va[i - 1].count(chr(ord('a') + j)):
                s += chr(ord('a') + j)
    return s

def check(v, s, s2, f):
    s3 = String2(v[0])
    for i in range(1, len(v)):
        s3 = s3 + String2(v[i])
    le = len(v[0])
    cur = String2('', String2('', f - String2(s)) - String2(s2)) * le
    for i in range(le - 2):
        cur = cur + (String2(s[i]) * (i + 1)) + (String2(s2[-i]) * (i + 1))
    cur = cur + (String2(s[le - 2]) * (le - 1)) + (String2(s[le - 1]) * le)
    e = cur - s3
    for i in range(26):
        if e[i]:
            return chr(ord('a') + i)

def main():
    va = req(1, n)
    va2 = req(1, n // 2 + n % 2)
    va3 = req(2, n // 2 + n % 2)
    # bukvi2 = compute(va2)
    # bukvi3 = compute(va3)
    if n == 1:
        print('!', va[0][0])
        return
    ma = [[] for i in range(n * 2)]
    for i in va:
        ma[len(i)].append(i)
    a = String2(''.join(ma[1]))
    s = solve(va2, va3)
    s2 = ''
    for i in range(2, n // 2 + 1):
        s2 = check(ma[i], s, s2, a) + s2
    se = String2('', a - String2(s)) - String2(s2)
    for i in range(len(se)):
        if se[i]:
            s += chr(ord('a') + i)
            break
    print('!', s + s2)

n = int(input())
main()


from sys import stdout

class String2:
    def __init__(self, s, b=None):
        if b is not None:
            self.b = b
            return
        self.b = [0] * 26
        for i in s:
            self.b[ord(i) - ord('a')] += 1

    def __add__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] += other.b[i]
        return String2('', b)

    def __sub__(self, other):
        b = self.b.copy()
        for i in range(26):
            b[i] -= other.b[i]
        return b

    def __mul__(self, other):
        ans = String2('', self.b)
        for i in range(26):
            ans.b[i] *= other
        return ans

def req(l, r, k=0):
    print('?', l, r)
    v = [''.join(sorted(input())) for i in range((r - l + 1) * (r - l + 2) // 2)]
    stdout.flush()
    return v

def compute(v):
    bukvi = [[0] * (n + 2) for _ in range(26)]
    for el in v:
        cur = len(el)
        for e in el:
            bukvi[ord(e) - ord('a')][cur] += 1
    return bukvi

def compute2(bukvi):
    bukvis = [set() for i in range(n + 2)]
    for i in range(26):
        prev = bukvi[i][1]
        for j in range(1, n // 2 + n % 2 + 1):
            while bukvi[i][j] != prev:
                bukvis[j].add(chr(ord('a') + i))
                prev += 1
    return bukvis

def solve(va, va2):
    for i in va2:
        va.remove(i)
    va.sort(key=len)
    s = va[0]
    for i in range(1, len(va)):
        for j in range(26):
            if va[i].count(chr(ord('a') + j)) != va[i - 1].count(chr(ord('a') + j)):
                s += chr(ord('a') + j)
    return s

def check(v, s, s2, f):
    s3 = String2(v[0])
    for i in range(1, len(v)):
        s3 = s3 + String2(v[i])
    le = len(v[0])
    cur = String2('', String2('', f - String2(s)) - String2(s2)) * le
    for i in range(le - 2):
        cur = cur + (String2(s[i]) * (i + 1)) + (String2(s2[-i-1]) * (i + 1))
    cur = cur + (String2(s[le - 2]) * (le - 1)) + (String2(s[le - 1:]) * le)
    e = cur - s3
    for i in range(26):
        if e[i]:
            return chr(ord('a') + i)

def main():
    if n == 1:
        va = req(1, 1)
        print('!', va[0])
        return
    elif n == 2:
        va2 = req(1, 1)
        va3 = req(2, 2)
        print('!', va2[0] + va3[0])
        return
    elif n == 3:
        va = req(1, 1)
        va2 = req(2, 2)
        va3 = req(3, 3)
        print('!', va[0] + va2[0] + va3[0])
        return
    va = req(1, n)
    va2 = req(1, max(n // 2 + n % 2, 2))
    va3 = req(2, max(n // 2 + n % 2, 2))
    # bukvi2 = compute(va2)
    # bukvi3 = compute(va3)
    ma = [[] for i in range(n * 2)]
    for i in va:
        ma[len(i)].append(i)
    a = String2(''.join(ma[1]))
    s = solve(va2, va3)
    s2 = ''
    for i in range(2, n // 2 + 1):
        s2 = check(ma[i], s, s2, a) + s2
    se = String2('', a - String2(s)) - String2(s2)
    for i in range(len(se)):
        if se[i]:
            s += chr(ord('a') + i)
            break
    print('!', s + s2)

n = int(input())
main()


import java.util.*
import kotlin.math.max
import kotlin.math.min

fun main() {
    val jin = Scanner(System.`in`)
    val n = jin.nextInt()
    if (n == 1) {
        println("? 1 1")
        println("! ${jin.next()}")
        return
    }
    val a = 1
    val b = n / 2
    println("? $a $b")
    val pos1 = getPossible(jin, b - a + 1)
    val c = b + 1
    val d = n
    println("? $c $d")
    val pos2 = getPossible(jin, d - c + 1)
    val e = max(n / 4, 1)
    val f = (3 * (n + 1)) / 4
    println("? $e $f")
    val pos3 = getPossible(jin, f - e + 1)
    for (s in pos1) {
        for (t in pos2) {
            for (u in pos3) {
                if (s.substring(e - 1) == u.substring(0, b - e + 1) && u.substring(c - e) == t.substring(0, f - c + 1)) {
                    println("! ${s + t}")
                    return
                } /*else {
                    println("$s $u $t")
                    println(">> ${s.substring(e - 1)} ${u.substring(0, b - e + 1)} ${u.substring(c - e)} ${t.substring(0, f - (c - e))}")
                }*/
            }
        }
    }
    println("fail")
}

fun getPossible(jin: Scanner, l: Int): List<String> {
    val freq = Array(26) { IntArray(l + 1) }
    val substrings = mutableMapOf<String, Int>()
    for (j in 1..(l * (l + 1)) / 2) {
        var s = jin.next().toList().sorted().joinToString("")
        substrings[s] = (substrings[s] ?: 0) + 1
        for (chara in s) {
            freq[chara - 'a'][s.length]++
        }
    }
    val pos = Array(((l + 1) / 2) + 1) { mutableListOf<Int>() }
    for (j in 0..25) {
        val times = IntArray(((l + 1) / 2) + 1)
        var occ = freq[j][1]
        for (k in 2..(l + 1) / 2) {
            while (freq[j][k] < occ * k) {
                times[k - 1]++
                occ--
                for (a in k..(l + 1) / 2) {
                    freq[j][a] -= k - 1
                }
            }
        }
        times[(l + 1) / 2] += occ
        for (k in 1..(l + 1) / 2) {
            if (times[k] > 0) {
                pos[k].add(j)
            }
        }
    }
    val res = mutableListOf<String>()
    val stack = Stack<Node>()
    stack.push(Node("", mutableMapOf()))
    while (!stack.isEmpty()) {
        val node = stack.pop()
        //println(node)
        if (node.s.length == l) {
            res.add(node.s)
        } else {
            val j = min(node.s.length + 1, l - node.s.length)
            for (c in pos[j]) {
                val chara = 'a' + c
                val next = Node(node.s + chara, node.substrings.toMutableMap())
                var works = true
                for (k in next.s.length - 1 downTo 0) {
                    var s = next.s.substring(k).toList().sorted().joinToString("")
                    next.substrings[s] = (next.substrings[s] ?: 0) + 1
                    if (next.substrings[s]!! > (substrings[s] ?: 0)) {
                        works = false
                        break
                    }
                }
                if (works) {
                    stack.push(next)
                }
            }
        }
    }
    return res
}

data class Node(val s: String, val substrings: MutableMap<String, Int>)


import java.util.*
import kotlin.math.max
import kotlin.math.min

fun main() {
    val jin = Scanner(System.`in`)
    val n = jin.nextInt()
    if (n == 1) {
        println("? 1 1")
        println("! ${jin.next()}")
        return
    } else if (n == 2) {
        println("? 1 1")
        val s = jin.next()
        println("? 2 2")
        val t = jin.next()
        println("! $s$t")
        return
    }
    val a = 1
    val b = (n - 1) / 2
    println("? $a $b")
    val pos1 = getPossible(jin, b - a + 1)
    val c = b + 1
    val d = n
    println("? $c $d")
    val pos2 = getPossible(jin, d - c + 1)
    val e = max((b - 1) / 2, 1)
    val f = b + ((d - b) / 2)
    println("? $e $f")
    val pos3 = getPossible(jin, f - e + 1)
    for (s in pos1) {
        for (t in pos2) {
            for (u in pos3) {
                if (s.substring(e - 1) == u.substring(0, b - e + 1) && u.substring(c - e) == t.substring(0, f - c + 1)) {
                    println("! ${s + t}")
                    return
                }
                //println("$s $u $t")
                //println(">> ${s.substring(e - 1)} ${u.substring(0, b - e + 1)} ${u.substring(c - e)} ${t.substring(0, f - (c - e))}")
            }
        }
    }
    println("fail")
}

fun getPossible(jin: Scanner, l: Int): List<String> {
    val freq = Array(26) { IntArray(l + 1) }
    val substrings = mutableMapOf<Long, Int>()
    for (j in 1..(l * (l + 1)) / 2) {
        var s = jin.next().toList().sorted().joinToString("")
        var hash = 0L
        for (chara in s) {
            hash += 1L shl (2 * (chara - 'a'))
        }
        hash += s.length.toLong() shl 60
        substrings[hash] = (substrings[hash] ?: 0) + 1
        for (chara in s) {
            freq[chara - 'a'][s.length]++
        }
    }
    val pos = Array(((l + 1) / 2) + 1) { mutableListOf<Int>() }
    for (j in 0..25) {
        val times = IntArray(((l + 1) / 2) + 1)
        var occ = freq[j][1]
        for (k in 2..(l + 1) / 2) {
            while (freq[j][k] < occ * k) {
                times[k - 1]++
                occ--
                for (a in k..(l + 1) / 2) {
                    freq[j][a] -= k - 1
                }
            }
        }
        times[(l + 1) / 2] += occ
        for (k in 1..(l + 1) / 2) {
            if (times[k] > 0) {
                pos[k].add(j)
            }
        }
    }
    val res = mutableListOf<String>()
    val stack = Stack<Node>()
    stack.push(Node("", mutableMapOf()))
    while (!stack.isEmpty()) {
        val node = stack.pop()
        //println(node)
        if (node.s.length == l) {
            res.add(node.s)
        } else {
            val j = min(node.s.length + 1, l - node.s.length)
            for (c in pos[j]) {
                val chara = 'a' + c
                val next = Node(node.s + chara, node.substrings.toMutableMap())
                var works = true
                var hash = 0L
                for (k in next.s.length - 1 downTo 0) {
                    hash += 1L shl 60
                    hash += 1L shl (2 * (next.s[k] - 'a'))
                    next.substrings[hash] = (next.substrings[hash] ?: 0) + 1
                    if (next.substrings[hash]!! > (substrings[hash] ?: 0)) {
                        works = false
                        break
                    }
                }
                if (works) {
                    stack.push(next)
                }
            }
        }
    }
    return res
}

data class Node(val s: String, val substrings: MutableMap<Long, Int>)
/*
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
 */


import java.util.*
import kotlin.math.min

fun main() {
    val jin = Scanner(System.`in`)
    val n = jin.nextInt()
    if (n == 1) {
        println("? 1 1")
        println("! ${jin.next()}")
        return
    } else if (n == 2) {
        println("? 1 1")
        val s = jin.next()
        println("? 2 2")
        val t = jin.next()
        println("! $s$t")
        return
    }
    val a = 1
    val b = n / 2
    println("? $a $b")
    val pos1 = getPossible(jin, b - a + 1)
    val c = b + 1
    val d = n
    println("? $c $d")
    val pos2 = getPossible(jin, d - c + 1)
    val e = (b + 3) / 2
    val f = b + ((d - b) / 2)
    println("? $e $f")
    val pos3 = getPossible(jin, f - e + 1)
    for (s in pos1) {
        for (t in pos2) {
            for (u in pos3) {
                if (s.substring(e - 1) == u.substring(0, b - e + 1) && u.substring(c - e) == t.substring(0, f - c + 1)) {
                    println("! ${s + t}")
                    return
                } /*else {
                    println("$s $u $t")
                    println(">> ${s.substring(e - 1)} ${u.substring(0, b - e + 1)} ${u.substring(c - e)} ${t.substring(0, f - (c - e))}")
                }*/
            }
        }
    }
    println("fail")
}

fun getPossible(jin: Scanner, l: Int): List<String> {
    val freq = Array(26) { IntArray(l + 1) }
    val substrings = mutableMapOf<Long, Int>()
    for (j in 1..(l * (l + 1)) / 2) {
        var s = jin.next().toList().sorted().joinToString("")
        var hash = 0L
        for (chara in s) {
            hash += 1L shl (2 * (chara - 'a'))
        }
        hash += s.length.toLong() shl 60
        substrings[hash] = (substrings[hash] ?: 0) + 1
        for (chara in s) {
            freq[chara - 'a'][s.length]++
        }
    }
    val pos = Array(((l + 1) / 2) + 1) { mutableListOf<Int>() }
    for (j in 0..25) {
        val times = IntArray(((l + 1) / 2) + 1)
        var occ = freq[j][1]
        for (k in 2..(l + 1) / 2) {
            while (freq[j][k] < occ * k) {
                times[k - 1]++
                occ--
                for (a in k..(l + 1) / 2) {
                    freq[j][a] -= k - 1
                }
            }
        }
        times[(l + 1) / 2] += occ
        for (k in 1..(l + 1) / 2) {
            if (times[k] > 0) {
                pos[k].add(j)
            }
        }
    }
    val res = mutableListOf<String>()
    val stack = Stack<Node>()
    stack.push(Node("", mutableMapOf()))
    while (!stack.isEmpty()) {
        val node = stack.pop()
        //println(node)
        if (node.s.length == l) {
            res.add(node.s)
        } else {
            val j = min(node.s.length + 1, l - node.s.length)
            for (c in pos[j]) {
                val chara = 'a' + c
                val next = Node(node.s + chara, node.substrings.toMutableMap())
                var works = true
                var hash = 0L
                for (k in next.s.length - 1 downTo 0) {
                    hash += 1L shl 60
                    hash += 1L shl (2 * (next.s[k] - 'a'))
                    next.substrings[hash] = (next.substrings[hash] ?: 0) + 1
                    if (next.substrings[hash]!! > (substrings[hash] ?: 0)) {
                        works = false
                        break
                    }
                }
                if (works) {
                    stack.push(next)
                }
            }
        }
    }
    return res
}

data class Node(val s: String, val substrings: MutableMap<Long, Int>)
/*
pppnppnnppnnnnppnnpnnpppnpnpnnnppnppnnpnpnpppnppnppnpppnnnpppnpnnnppnpnpnpnppnpnnpnpppnppnnpppnnppn
 */


import java.util.*
import kotlin.math.min

fun main() {
    val jin = Scanner(System.`in`)
    val n = jin.nextInt()
    val a = 1
    val b = n / 2
    println("? $a $b")
    val pos1 = getPossible(jin, b - a + 1)
    val c = b + 1
    val d = n
    println("? $c $d")
    val pos2 = getPossible(jin, d - c + 1)
    val e = n / 4
    val f = (3 * (n + 1)) / 4
    println("? $e $f")
    val pos3 = getPossible(jin, f - e + 1)
    for (s in pos1) {
        for (t in pos2) {
            for (u in pos3) {
                if (s.substring(e - 1) == u.substring(0, b - e + 1) && u.substring(c - e) == t.substring(0, f - (c - e))) {
                    println("! ${s + t}")
                    return
                } else {
                    println("$s $u $t")
                }
            }
        }
    }
    println("fail")
}

fun getPossible(jin: Scanner, l: Int): List<String> {
    val freq = Array(26) { IntArray(l + 1) }
    val substrings = mutableMapOf<String, Int>()
    for (j in 1..(l * (l + 1)) / 2) {
        var s = jin.next().toList().sorted().joinToString("")
        substrings[s] = (substrings[s] ?: 0) + 1
        for (chara in s) {
            freq[chara - 'a'][s.length]++
        }
    }
    val pos = Array(((l + 1) / 2) + 1) { mutableListOf<Int>() }
    for (j in 0..25) {
        val times = IntArray(((l + 1) / 2) + 1)
        var occ = freq[j][1]
        for (k in 2..(l + 1) / 2) {
            while (freq[j][k] < occ * k) {
                times[k - 1]++
                occ--
                for (a in k..(l + 1) / 2) {
                    freq[j][a] -= k - 1
                }
            }
        }
        times[(l + 1) / 2] += occ
        for (k in 1..(l + 1) / 2) {
            if (times[k] > 0) {
                pos[k].add(j)
            }
        }
    }
    val res = mutableListOf<String>()
    val stack = Stack<Node>()
    stack.push(Node("", mutableMapOf()))
    while (!stack.isEmpty()) {
        val node = stack.pop()
        //println(node)
        if (node.s.length == l) {
            res.add(node.s)
        } else {
            val j = min(node.s.length + 1, l - node.s.length)
            for (c in pos[j]) {
                val chara = 'a' + c
                val next = Node(node.s + chara, node.substrings.toMutableMap())
                var works = true
                for (k in next.s.length - 1 downTo 0) {
                    var s = next.s.substring(k).toList().sorted().joinToString("")
                    next.substrings[s] = (next.substrings[s] ?: 0) + 1
                    if (next.substrings[s]!! > (substrings[s] ?: 0)) {
                        works = false
                        break
                    }
                }
                if (works) {
                    stack.push(next)
                }
            }
        }
    }
    return res
}

data class Node(val s: String, val substrings: MutableMap<String, Int>)


import java.util.*
import kotlin.math.max
import kotlin.math.min

fun main() {
    val jin = Scanner(System.`in`)
    val n = jin.nextInt()
    if (n == 1) {
        println("? 1 1")
        println("! ${jin.next()}")
        return
    } else if (n == 2) {
        println("? 1 1")
        val s = jin.next()
        println("? 2 2")
        val t = jin.next()
        println("! $s$t")
        return
    }
    val a = 1
    val b = n / 2
    println("? $a $b")
    val pos1 = getPossible(jin, b - a + 1)
    val c = b + 1
    val d = n
    println("? $c $d")
    val pos2 = getPossible(jin, d - c + 1)
    val e = (b + 3) / 2
    val f = b + ((d - b) / 2)
    println("? $e $f")
    val pos3 = getPossible(jin, f - e + 1)
    for (s in pos1) {
        for (t in pos2) {
            for (u in pos3) {
                if (s.substring(e - 1) == u.substring(0, b - e + 1) && u.substring(c - e) == t.substring(0, f - c + 1)) {
                    println("! ${s + t}")
                    return
                } /*else {
                    println("$s $u $t")
                    println(">> ${s.substring(e - 1)} ${u.substring(0, b - e + 1)} ${u.substring(c - e)} ${t.substring(0, f - (c - e))}")
                }*/
            }
        }
    }
    println("fail")
}

fun getPossible(jin: Scanner, l: Int): List<String> {
    val freq = Array(26) { IntArray(l + 1) }
    val substrings = mutableMapOf<String, Int>()
    for (j in 1..(l * (l + 1)) / 2) {
        var s = jin.next().toList().sorted().joinToString("")
        substrings[s] = (substrings[s] ?: 0) + 1
        for (chara in s) {
            freq[chara - 'a'][s.length]++
        }
    }
    val pos = Array(((l + 1) / 2) + 1) { mutableListOf<Int>() }
    for (j in 0..25) {
        val times = IntArray(((l + 1) / 2) + 1)
        var occ = freq[j][1]
        for (k in 2..(l + 1) / 2) {
            while (freq[j][k] < occ * k) {
                times[k - 1]++
                occ--
                for (a in k..(l + 1) / 2) {
                    freq[j][a] -= k - 1
                }
            }
        }
        times[(l + 1) / 2] += occ
        for (k in 1..(l + 1) / 2) {
            if (times[k] > 0) {
                pos[k].add(j)
            }
        }
    }
    val res = mutableListOf<String>()
    val stack = Stack<Node>()
    stack.push(Node("", mutableMapOf()))
    while (!stack.isEmpty()) {
        val node = stack.pop()
        //println(node)
        if (node.s.length == l) {
            res.add(node.s)
        } else {
            val j = min(node.s.length + 1, l - node.s.length)
            for (c in pos[j]) {
                val chara = 'a' + c
                val next = Node(node.s + chara, node.substrings.toMutableMap())
                var works = true
                for (k in next.s.length - 1 downTo 0) {
                    var s = next.s.substring(k).toList().sorted().joinToString("")
                    next.substrings[s] = (next.substrings[s] ?: 0) + 1
                    if (next.substrings[s]!! > (substrings[s] ?: 0)) {
                        works = false
                        break
                    }
                }
                if (works) {
                    stack.push(next)
                }
            }
        }
    }
    return res
}

data class Node(val s: String, val substrings: MutableMap<String, Int>)
/*
pppnppnnppnnnnppnnpnnpppnpnpnnnppnppnnpnpnpppnppnppnpppnnnpppnpnnnppnpnpnpnppnpnnpnpppnppnnpppnnppn
 */