#pragma GCC optimize("Ofast,unroll-loops")
#include <bits/stdc++.h>
using namespace std;
void main_program();
signed main(){
ios_base::sync_with_stdio(0); cin.tie(0);
main_program();
}
using u8 = uint8_t;
template <class T>
using uset = unordered_set<T>;
int n, M, L;
int n_div_10;
string s;
vector<int> mapping, answer;
vector<vector<int>> A;
int newval = 0;
void create_mapping(){
mapping.assign(256, 0);
int crr = 0;
for (char c = 'a'; c <= 'z'; c++) mapping[c] = crr++;
for (char c = 'A'; c <= 'Z'; c++) mapping[c] = crr++;
for (char c = '0'; c <= '9'; c++) mapping[c] = crr++;
for (char c: "~!@#$%^&()[]`:;\"'<>,.?/|\\=-{}") mapping[c] = crr++;
}
int remaining = 0;
map<pair<int, int>, bool> cache;
bool equivalent(int a, int b){
if (a > b) swap(a, b);
if (cache.count({a, b})) return cache[{a, b}];
int cnt = 0;
int it1 = 0, it2 = 0;
while (it1 < L && it2 < L){
if (A[a][it1] == A[b][it2]){
it1++; it2++; cnt++;
}
else if (A[a][it1] < A[b][it2]) it1++;
else it2++;
if (remaining >= max(100, n_div_10) && it1 >= 6 && cnt == 0) return false;
}
return cache[{a, b}] = (cnt >= L/2);
}
using u32 = uint32_t;
u32 faster_rand(){
static u32 state = 2945725598;
state += 9877345679;
u32 z = state;
z = (z ^ (z >> 14)) * 897698346;
z = (z ^ (z >> 12)) * 797536943;
return z ^ (z >> 15);
}
vector<int> vec;
void run_hash_function(){
for (auto &val: vec) val = faster_rand();
}
void main_program(){
cin >> n >> M >> L;
n_div_10 = n / 10;
cin >> s;
create_mapping();
answer.assign(n * 2, -1);
A.assign(n * 2, vector<int>(L));
map<int, vector<int>> mp;
map<int, int> compress;
for (int i = 0; i < n * 2; i++){
for (int j = 0; j < L; j++){
int H = 0;
for (int k = 0; k < 4; k++){
H = H * 92 + mapping[s[i * L * 4 + j * 4 + k]];
}
mp[H].push_back(i);
A[i][j] = H;
compress[H] = 1;
}
}
for (auto &[key, value]: compress) value = newval++;
for (int i = 0; i < n * 2; i++){
for (int j = 0; j < L; j++){
A[i][j] = compress[A[i][j]];
}
}
for (int i = 0; i < n * 2; i++){
sort(A[i].begin(), A[i].end());
}
// for (int i = 0; i < n * 2; i++){
// for (int j = 0; j < L; j++){
// cout << A[i][j] << " ";
// }
// cout << "\n";
// }
vec.resize(newval);
remaining = n;
vector<pair<int, int>> group(n * 2);
while (remaining){
// cout << "New iteration" << endl;
vector<int> order;
run_hash_function();
for (int i = 0; i < n * 2; i++){
if (answer[i] >= 0) continue;
group[i] = {INT_MAX, INT_MAX};
order.push_back(i);
for (int j = 0; j < L; j++){
group[i].first = min(group[i].first, vec[A[i][j]]);
}
}
int crridx = 0;
run_hash_function();
for (int i = 0; i < n * 2; i++){
for (int j = 0; j < L; j++){
group[crridx].second = min(group[crridx].second, vec[A[i][j]]);
}
crridx++;
}
shuffle(order.begin(), order.end());
sort(order.begin(), order.end(), [&](int idx1, int idx2){
return group[idx1] < group[idx2];
});
// for (auto idx: order) cout << "idx = " << idx << endl;
int sz = order.size();
for (int i = 0; i < sz; i++){
int ii = order[i];
if (answer[ii] >= 0) continue;
for (int j = i + 1; j < min(i + 4, sz); j++){
int jj = order[j];
if (group[jj] != group[ii]) break;
if (equivalent(ii, jj)){
answer[ii] = jj;
answer[jj] = ii;
remaining--;
break;
}
}
}
}
for (int i = 0; i < n * 2; i++){
cout << answer[i] + 1 << "\n";
}
}