Показаны сообщения с ярлыком динамические структуры данных. Показать все сообщения
Показаны сообщения с ярлыком динамические структуры данных. Показать все сообщения

пятница, 25 октября 2019 г.

Сортировка по возрастанию с помощью бинарного дерева

В С++ в STL нет контейнера для формирования динамической структуры бинарное дерево. Поэтому используют структуру с полями указателями на левое и правое поддерево:

typedef struct TNode *PNode;
struct TNode
{
    int data;
    PNode left;
    PNode right;
} ;
Будем вводить элементы массива и сразу формировать дерево:
Функция добавления звена в дерево:
void MakeTree(int k, PNode &p)

 {
if (p==NULL) //если нет корня
    {
        p=new TNode;
        p->data=k;
        p->left=NULL;
        p->right=NULL;
    }
else {  if (k<=p->data)
// если меньше корня, то добавляем влево
        if (p->left!=NULL)
            MakeTree(k, p->left);
        else{
            p->left=new TNode;
            p->left->left=NULL;
            p->left->right=NULL;
            p->left->data=k;
        }
    }
if (k>p->data)
//если больше корня, то добавляем вправо
    {if (p->right!=NULL)
            MakeTree(k, p->right);
        else        {
            p->right=new TNode;
            p->right->left=NULL;
            p->right->right=NULL;
            p->right->data=k;        }
    }
}
}
Функции обхода дерева:

void Search_LKP(PNode p)
{//левое-корень-правое
    if(p!=NULL)
    {
        Search_LKP(p->left);
        cout << p->data << '  ';
        Search_LKP(p->right);
     }
}
Вывод данных: 1 2 3 4 5 6 9
void Search_KLP(PNode p)
{//корень-левое-правое
    if(p!=NULL)
    {
        cout << p->data << ' ';
        Search_KLP(p->left);
        Search_KLP(p->right);
    }
}
Вывод данных: 4 2 1 3 6 5 9
void Search_LPK(PNode p)
{//левое-правое-корень
    if(p!=NULL)
    {
        Search_LPK(p->left);
        Search_LPK(p->right);
        cout << p->data << ' ';
    }
}
Вывод данных: 1 3 2 5 9 6 4
Функция удаления:
void DeleteTree(PNode &p)
{
    if(p->left!=NULL)
    {
        DeleteTree(p->left);
    }
    if(p->right!=NULL)
    {
        DeleteTree(p->right);
    }
    delete p;
}

Основная программа:

int main()

{
    PNode t;
    t=NULL;
    int x;
//ввод  до нуля  
    cin>>x;
    while(x!=0)
    {
        MakeTree(x, t);   cin>>x;
    }
//обход дерева
    Search_LKP(t);
//удаление дерева
    DeleteTree(t);
    return 0;
}

суббота, 6 января 2018 г.

Задание начального значения элементам массива

Очень часто при решении задач с массива требуется задать им начальное значение или обнулить. Самый простой способ использование цикла. Но есть и специальные функции, которые по указанному количеству байт задает им начальное значение.

В Паскале:

var A:array[1..100]of integer;
FillChar(A, 100*SizeOf(integer), 0);

Для строк:
Var S:string;
S:='';

или
Var S:string[100];
FillChar(S, SizeOf(S), ' ');

В С++:

int A[10]={0};
int B[10]={1};

Но для массива с переменной длиной не будет работать, надо так:

int n;
cin>>n;
fill(A, A+n, 0); 
Здесь A - хранит адрес начала массива, А+n - переносим указатель на n элементов, т.е. в конец массива, третий аргумент - чем заполнить массив, можно поставить 1.

fill(&A[0], &A[0]+n,0);
Здесь &A[0] - получаем адрес на начальный элемент массива с индексом 0.

Таким образом можно обнулять часть массива, например, начиная с середины:
fill(&A[n/2], &A[0]+n,0); 

Если это двумерный массив:
int n, m;
cin>>n>>m;
double A[n][m];
fill(&A[0][0], &A[0][0]+n*m, 0);

Если массив объявлен как vector:
vector <bool> A;
int n;
cin>>n;
A.resize(n,false);

или
fill(A.begin(), A.end(),true);



среда, 8 ноября 2017 г.

Вектор и ассоциативный массив при решении заданий 27 ЕГЭ

Задача взята из сборника задач на сайте kpolyakov.spb.ru под №72.

(Д.Ф. Муфаззалов) Имеется набор данных, состоящий из пар положительных целых чисел. Для каждой пары чисел находится значение А – наибольший общий делитель. Напишите эффективную по времени работы и по используемой памяти программу, которая будет определять, какое значение А встречалось чаще всего. Если несколько значений А встречалось одинаковое наибольшее количество раз, вывести их в порядке убывания.

Программа считается эффективной по времени, если время работы программы пропорционально количеству пар чисел N, т. е. при увеличении N в k раз время работы программы должно увеличиваться не более чем в k раз. Программа считается эффективной по памяти, если размер памяти, использованной в программе для хранения данных, не зависит от числа N и не превышает 100 килобайт.

Входные данные:
На вход программе в первой строке подаётся количество пар N (1 <= N <= 100000). Каждая из следующих N строк содержит два натуральных числа, не превышающих 1000.

Пример входных данных:
6
1 3
5 15
6 9
5 4
3 3
36 40 
 
Пример выходных данных для приведённого примера входных данных:
3 1

Решение:
Итак, необходимо минимизировать время и память.
Будем сразу искать НОД введенных чисел и считать их количество. Для этого будем использовать тип "множество" или "ассоциативный массив" или "словарь" (map), где будем накапливать количество встречаемых чисел НОД. Конечно, если таких значений будет по одному, то по памяти мы не сэкономим.
Далее применим алгоритм поиска  наибольшего значения (second) и вывода всех индексов (first) с этим значением. Вот пример кода с использованием обычного статического массива:

int k=0, a=0, A[n],C[1001];
for(i=0;i<n;i++)
{
  if(A[i]>a)
  {
     a=A[i];
     k=0;
     C[k]=i;
     k++;
  }
  else if(A[i]==a)
  {
      C[k]=i;
      k++;
  }
}

Будем хранить индексы (first - значение НОД) в "динамическом массиве" (вектор) - тип vector.
Так как в map числа хранятся в отсортированном виде по первому параметру (first), то заноситься они будут в возрастающем порядке. Поэтому при выводе значений нужно это учесть и выводить с последнего элемента. 

Программа на С++:
#include <iostream>
#include <map>
#include <iterator>
#include <vector>
using namespace std;
int main()
{
int A,i,x,y,n;
map<int,int>B;
map<int,int>::iterator it;
vector<int>C;
cin>>n;
for(i=0;i<n;i++)
{
  cin>>x>>y;
  while(x>0&&y>0)
  {
   if(x>y)x=x%y;
   else y=y%x;
  }
  B[x+y]++;//заносим данные в ассоциативный массив
}

A=0;
for(it=B.begin(); it!=B.end(); it++)
 if(it->second>A) //ищем наибольшее значение
 {
  A=it->second;
  C.clear(); //очищаем от старых данных
  C.push_back(it->first);//и заносим в динамический массив (вектор)
 }
 else if(it->second==A) //если максимумов несколько,
  C.push_back(it->first); //то добавим в конец массива
//выводим значения массива в обратном порядке
for(i=C.size()-1;i>=0;i--)
  cout<<C[i]<<" ";
return 0;
}

Экономия по времени и по памяти небольшая, но зато применили знания динамических структур.

суббота, 30 апреля 2016 г.

Стек и очередь в Си

В языке Си (не С++) нет таких конструкций как класс и соответственно нет класса для работы с очередью и стеком. Но есть тип struct (структура), которая похожа на тип class, но без возможности включения методов (функций) в него. С помощью структуры можно описать все динамические структуры: стек, очередь, дек, списки, деревья.
В Си для выделения динамической памяти используется функция malloc, а для освобождения - функция free. В С++ для этих целей используется оператор new и delete соответственно.

Рассмотрим следующую задачу:
Дан набор числовых и символьных величин. Все числа целые, а символ всегда один. Символы и числа разделены одним пробелом. Необходимо вывести числа в обратном порядке, а символы - как они поступали на вход, т.е. в прямом порядке.

В этой задаче сформулируем несколько алгоритмических проблем:
  1. Как прочитать все данные, где будет конец ввода?
  2. Как отделить числа от символов? А если число состоит из нескольких цифр или оно отрицательное?
  3. Как вывести в обратном и прямом порядке? Какие динамические структуры нам в этом помогут?
Начнем отвечать на вопросы и составлять алгоритм.

1. Как прочитать все данные, где будет конец ввода?
В задаче не указано, когда закончиться набор данных, но можно предположить, что данные могут храниться в файле. Тогда считываем данные до конца файла (код конца файла можно определить с помощью функции EOF()). А если этот ввод производить с клавиатуры - то можно ввести код конца файла Ctrk+Z и Enter.
В более простом случае - можно вводить только строку. Тогда кодом конца строки будет символы с кодом 10 и 13, и на клавиатуре - это клавиша Enter. Для такой реализации можно в цикле while считывать символы последовательно до тех пор, пока символы с кодом 10 и 13 не обнаружатся. А можно просто считать всю строку с помощью функции gets(адрес начала строки) и далее в цикле for просматривать все символы последовательно до полученной длины строки.

2. Как отделить числа от символов? А если число состоит из нескольких цифр или оно отрицательное?
В этом случае посимвольное чтение символов должно сопровождаться проверкой, что:
  • первый символ может быть минусом, после которого стоит любая цифра;
  • цифра - это символ, который принадлежит диапазону от '0' до '9';
  • после числа стоит один пробел.
Все эти условия можно проверить последовательно, используя некий флаг - переменная отвечающая за то, что мы считываем число, и "собирать" в числовую строку, которую потом с помощью функции atoi можно преобразовать в целый тип. Если флаг нулевой, то это не число. Важно после цикла проверить этот флаг, так как может оказаться, что последним был введен не символ, а число. И необходимо его сохранить. 

3. Как вывести в обратном и прямом порядке? Какие динамические структуры нам в этом помогут?
Если нужно вывести в обратном порядке, то это стек, а если в прямом, то - очередь. 

Реализация стека:
Опишем структуру (struct) данных, которая содержит информационную часть (то, что хранится в стеке) и указатель (адрес ячейки) на следующий элемент в стеке. 

struct STACK
{
  int a;
  struct STACK *next;
};
Кроме этого нужна переменная - указатель на начало (верхушка) стека. В начале программы указатель ни на что не указывает и имеет нулевой адрес NULL.

struct STACK *top=NULL;

Напишем два метода (функции): добавить/затолкать элемент (push) и удалить/вытолкнуть (pop) элемент.

Добавление элемента в стек:

void push(int c, struct STACK **b)
{
 
struct STACK *temp = (struct STACK*) malloc(sizeof(struct STACK));
  temp->a = c;
  temp->next = (*b);
  (*b) = temp;
}


Удаление элемента из стека:
int pop(struct STACK **t)
{
  if ((*t)!=NULL)
  {
    struct STACK *temp=(*t);
    int a = (*t)->a;
    (*t) = (*t)->next;
    free(temp);
    return a;
  }
  else
    return 0;
}

Реализация очереди:
Опишем структуру (struct) данных, которая содержит информационную часть (то, что хранится в очереди) и указатель (адрес ячейки) на следующий элемент в очереди.

struct QUEUE
{
  char a;
  struct QUEUE *next;
};

Кроме этого нужны две переменные - указатель на начало (голова) очереди и конец (хвост) очереди. В начале программы указатели ни на что не указывают и имеют нулевой адрес NULL.

struct QUEUE *head=NULL, *tail=NULL;

Напишем два метода (функции): добавить/затолкать элемент в конец очереди (push_back) и удалить/вытолкнуть элемент из начала очереди (pop_front).

Поставить в очередь:


void push_back(char c, struct QUEUE **b)
{
  if((*b)!=NULL)
  {
    struct QUEUE *temp=(struct QUEUE*) malloc(sizeof(struct QUEUE));
    temp->a = c;
    temp->next = NULL;
    (*b)->n=temp;
    (*b)=temp;
  }
  else
  {
    (*b) = (struct QUEUE*) malloc(sizeof(struct QUEUE));
    (*b)->a = c;
    (*b)->next = NULL;
     head=(*b);
  }
}


Удалить из очереди:

char pop_front(struct QUEUE **t)
{
  if ((*t)!=NULL)
  {
    struct QUEUE *temp=(*t);
    char a = (*t)->a;
    (*t) = (*t)->next;
    free(temp);
    return a;
  }
  else
    return 0;
}

Вывод в прямом и обратном порядке соответствует операции удаления (выталкивания) элемента из очереди или стека соответственно до тех пор, пока не будет элементов или пока указатель на начало очереди или стека не станет нулевым (пока не на что будет указывать):

    while(head!=NULL)
        printf("%c ", pop_front(&head));

    while(top!=NULL)
        printf("%d ", pop(&top));

Приведем полный код программы:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

struct STACK
{
    int a;
    struct STACK *next;
};
struct STACK *top=NULL;

void push(int c, struct STACK **b)
{
        struct STACK *temp = (struct STACK*) malloc(sizeof(struct STACK));
        temp->a = c;
        temp->next = (*b);
        (*b)=temp;
}
int pop(struct STACK **t)
{
    if ((*t)!=NULL)
    {
        struct STACK *temp=(*t);
        int a = (*t)->a;
        (*t) = (*t)->next;
        free(temp);
        return a;
    }
    else
        return 0;
}

struct QUEUE
{
    char a;
    struct QUEUE *next;
};

struct QUEUE *head=NULL, *tail=NULL;

void push_back(char c, struct QUEUE **b)
{
    if((*b)!=NULL)
    {
        struct QUEUE *temp=(struct QUEUE*) malloc(sizeof(struct QUEUE));
        temp->a = c;
        temp->next = NULL;
        (*b)->next=temp;
        (*b)=temp;
    }
    else
    {
        (*b) = (struct QUEUE*) malloc(sizeof(struct QUEUE));
        (*b)->a = c;
        (*b)->next = NULL;
        head=(*b);
    }
}
char pop_front(struct QUEUE **t)
{
    if ((*t)!=NULL)
    {
        struct QUEUE *temp=(*t);
        char a = (*t)->a;
        (*t) = (*t)->next;
        free(temp);
        return a;
    }
    else
        return 0;
}

int main()
{
    char c[200], str[10];
    gets(c);
    int a, k=0;
    for (int i=0; i<strlen(c); i++)
    {
        if (c[i]=='-' && c[i+1]>='0' && c[i+1]<='9' && k==0) k=1;
        else
        if (c[i]>='0' && c[i]<='9') k=1;
        else
        if (c[i]==' ' && k==1)
        {
            k=0;
            strncpy(str,c,i+1);
            strcpy(c,&(c[i+1]));
            i=-1;
            a=atoi(str);
            push(a,&top);
        }
        else
        {
            if (c[i]!=' ') push_back(c[i], &tail);
            strcpy(c,&(c[i+1]));
            i=-1;
        }

    }
    if(k==1)
    {
        a=atoi(c);
        push(a,&top);
    }

    while(head!=NULL)
        printf("%c ", pop_front(&head));

    while(top!=NULL)
        printf("%d ", pop(&top));

    return 0;
}

понедельник, 4 мая 2015 г.

Использование map в С++

Очень часто на ЕГЭ при решении задач №27(С4)  и на олимпиадах требуется:
  • определить количество уникальных слов в тексте, в котором их количество заранее не известно;
  • найти частоту появления символов или слов (провести частотный анализ);
  • определить по заданному словарю закодированный текст;
  • вывести слова в алфавитном порядке. 
Такие задачи можно решить с помощью массива, а еще лучше - с использованием типа map в С++.

map - это параметризованный класс из библиотека STL в С++, который может применяться для описания множества или ассоциированного массива. В этом массиве вместо числового индекса может быть строка (first), а значение элементов массива - количество слов (повторений) в тексте (second). При этом индекс в массиве не может повторяться.

Описание:

map <string, int> L; 

Для вывода элементов массива - используется итератор (указатель на текущий элемент массива):

map <string, int>::iterator it;

Функции по работе с ассоциированным массивом:
  1. количество записей (содержимое элемента массива): int p=L.count ( s ); 
  2. добавление новой записи со значением 1: L.insert ( pair <string,int> (s, 1) );
    или L[s]=1;
  3. поиск записи по строке s: it = L.find(s);
  4. удаление записи по итератору: L.erase (it);
  5. количество элементов в массиве: int n=L.size();
  6. указатель на начало массива: it = L.begin();
  7. указатель на конец массива: it = L.end();
Приведем разбор таких заданий (задачи взяты здесь с сохранением нумерации).

44) На электронную почту Вам пришло письмо, подписанное аббревиатурой (первыми буквами фамилии, имени и отчества (далее - ФИО) отправителя). Аббревиатура оказалась Вам незнакома. У Вас есть список всех предполагаемых отправителей, взятый из ранее полученных писем, среди которых различных людей с такой аббревиатурой не больше 10.

Вам предлагается написать эффективную, в том числе по используемой памяти, программу, которая определит всех вероятных адресатов – людей, ФИО которых можно сократить до нужной аббревиатуры. ФИО следует выдать в порядке убывания частоты их встречаемости в списке.

На вход программе в первой строке подается аббревиатура – строка, состоящая из трех заглавных латинских букв. Во второй строке находится число N – количество ФИО, полученных в результате анализа почты, не все из них подходят под указанную аббревиатуру. Значение N может быть очень велико. В каждой из следующих N строк записано три слова: Фамилия Имя Отчество соответствующего человека. Слова разделяются одним пробелом. В конце и в начале строки пробелов нет. Все слова записаны заглавными латинскими буквами. Длина ФИО не превышает 100 символов. Гарантируется, что хотя бы один человек с нужной аббревиатурой есть.

Пример входных данных:
IPI
4
IVANOV PETR IVANOVICH
PETROV IVAN IVANOVICH
IVANOV PETR IVANOVICH
ILYIN PETR ILYICH

Программа должна вывести предполагаемых отправителей письма с указанием частоты их встречаемости в списке (в порядке убывания частоты).

Пример выходных данных для приведенного выше примера входных данных:
IVANOV PETR IVANOVICH 2
ILYIN PETR ILYICH 1

В этом примере map используется для частотного анализа слов.

Программа на С++:
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main()
{
    map<string,int>L;
    int a,n;
    string a1,S,s1,s2,s3;
    cin>>a1;//вводим аббревиатуру
    cin>>n; //вводим количество строк
    for (int i=0;i<n;i++)
    {
      cin>>s1>>s2>>s3; //вводим фамилию, имя, отчество
      S=s1+" "+s2+" "+s3; //образуем строку из фамилии, имени и отчества
      if(s1[0]==a1[0]&&a1[1]==s2[0]&&a1[2]==s3[0]) 
//если аббревиатура совпала, то 
           L[S]++; //добавим в список и/или увеличим счетчик
    }

    map<string,int>::iterator k, it;
    int m, i, l=L.size();
    for(i=0;i<l;i++) //пока есть еще элементы в списке
    {
      m=0;
      for (k=L.begin();k!=L.end();k++) //от начала до конца списка
        if(k->second>m) //если нашли максимальный - запомним
        {
         m=k->second; 
         S=k->first; 
         it=k;
        } 
       cout<<S<<" "<<m<<endl; //выведем
       L.erase(it); //удалим из списка
    }
    return 0;
}

40) Вам необходимо написать программу распознавания чисел, записанных прописью. Сначала на вход программе подается обучающий блок, состоящий из 27 строк. Первые 9 строк содержат слова «один», «два», ..., «девять», следующие 9 строк - слова «одиннадцать», «двенадцать», ... «девятнадцать», следующие 9 строк - слова «десять», «двадцать», ..., «девяносто». Все слова записаны маленькими русскими буквами без лишних пробелов в начале и в конце строки. 

Затем на вход программе подается значение N - количество записей, которые необходимо обработать. Следующие N строк содержат записанные словами числа. Каждое число записано по-русски, маленькими буквами, без ошибок. Если число состоит из нескольких слов, между словами находится ровно один пробел, лишних пробелов в начале и в конце строк нет. 

Напишите эффективную программу, которая определит сумму тех входных чисел, которые находятся в интервале от 1 до 99. 

Размер памяти, которую использует Ваша программа, не должен зависеть от длины исходного списка. 

Пример входных данных (обучающий блок показан в примере с сокращениями):
один
два
…
девяносто
5
двадцать восемь
два миллиона
четырнадцать
сто двадцать три
тысяча девятьсот восемьдесят четыре

Пример выходных данных для приведённого выше примера входных данных:
42 

В данном примере map нам нужен как словарь названий чисел с сохранением его значения.

Программа на С++:

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main()
{
    map<string,int>L;
    int t,i,a,n,m,r=0;
    string s;
//готовим словарь для обозначений чисел
    for(i=1;i<=9;i++)
    { 
       cin>>s;
       L[s]=i;
    } 
    for(i=11;i<=19;i++)
    { 
       cin>>s;
       L[s]=i;
    } 
    for(i=10;i<=90;i=i+10)
    { 
       cin>>s;
       L[s]=i;
    } 
    
    cin>>n; //вводим количество строк
    for (i=0;i<n;i++)
    {
      m=0;//число
      t=1;//флаг для неправильного числа
      do{
         cin>>s;//вводим слово-значение числа
         if (L.count(s)==0)//если слово отсутствует в словаре, то
           t=0;//меняем флаг
         m+=L[s];//собираем число по его значению
      }while (cin.get()!=10); //вводим до конца строки
      if(t==1 && m>=1 && m<100)//если число удовлетворяет условию,то
        r+=m; //добавляем к сумме
    }
    cout<<r; //вывод суммы
    return 0;
}

12) В некотором вузе абитуриенты проходили предварительное тестирование, по результатам которого они могут быть допущены к сдаче вступительных экзаменов в первом потоке. Тестирование проводится по трём предметам, по каждому предмету абитуриент может набрать от 0 100 баллов. При этом к сдаче экзаменов в первом потоке допускаются абитуриенты, набравшие по результатам тестирования не менее 30 баллов по каждому из трёх предметов, причём сумма баллов должна быть не менее 140. На вход программы подаются сведения о результатах предварительного тестирования. Известно, что общее количество участников тестирования не превосходит 500. 

В первой строке вводится количество абитуриентов, принимавших участие в тестировании, N. Далее следуют N строк, имеющих следующий формат: 

<Фамилия> <Имя> <Баллы>

Здесь <Фамилия> – строка, состоящая не более чем из 20 символов; <Имя> – строка, состоящая не более чем из 15 символов, <Баллы> – строка, содержащая два целых числа, разделенных пробелом – баллы, полученные на тестировании по каждому из трёх предметов. При этом <Фамилия> и <Имя>, <Имя> и <Баллы> разделены одним пробелом. Пример входной строки: 

Романов Вельямин 48 39 55

Напишите программу, которая будет выводить на экран фамилии и имена абитуриентов, допущенных к сдаче экзаменов в первом потоке. При этом фамилии должны выводиться в алфавитном порядке.

В данном примере map нужен для хранения отсортированного массива строк и быстрого его вывода.

Программа на С++:
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main()
{
    map<string,int>L;
    int a1,a2,a3,n,i;
    string S,s1,s2;
    cin>>n; //вводим количество строк
    for (i=0;i<n;i++)
    {
      cin>>s1>>s2>>a1>>a2>>a3; //вводим фамилию, имя и баллы за тест
      S=s1+" "+s2; //образуем строку из фамилии, имени
      
      if(a1>=30 && a2>=30 && a3>=30 && a1+a2+a3>=140) 
//если по баллам проходит в первый тур, то 
           L[S]=1; //добавим в список
    }

    map<string,int>::iterator k;
    for (k=L.begin();k!=L.end();k++) //от начала до конца списка
        сout<<k->first<<endl; //выведем строку

    return 0;
}

4) На вход программы подаются фамилии и имена учеников. Известно, что общее количество учеников не превосходит 100. В первой строке вводится количество учеников, принимавших участие в соревнованиях, N. Далее следуют N строк, имеющих следующий формат: 

<Фамилия> <Имя>

Здесь <Фамилия> – строка, состоящая не более чем из 20 символов; <Имя> – строка, состоящая не более чем из 15 символов. При этом <Фамилия> и <Имя> разделены одним пробелом. Примеры входных строк: 

Иванова Мария
Петров Сергей

Требуется написать программу, которая формирует и печатает уникальный логин для каждого ученика по следующему правилу: если фамилия встречается первый раз, то логин – это данная фамилия, если фамилия встречается второй раз, то логин – это фамилия, в конец которой приписывается число 2 и т.д. Например, для входной последовательности 

Иванова Мария
Петров Сергей
Бойцова Екатерина
Петров Иван
Иванова Наташа 

будут сформированы следующие логины:

Иванова
Петров
Бойцова
Петров2
Иванова2

В данном примере map будет служить для хранения одинаковых фамилий и их повторений, что обеспечит быстрый вывод результата.

Программа на С++:
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main()
{
    map<string,int>L;
    int n,i;
    string s1,s2;
    cin>>n; //вводим количество строк
    for (i=0;i<n;i++)
    {
      cin>>s1>>s2; //вводим фамилию и имя
      L[s1]++; //добавляем в список фамилию и увеличиваем счетчик
      cout<<s1; //выводим фамилию
      if(L[s1]>1) //если фамилия уже встречалась ранее, то
        cout<<L[s1];//выводим порядковый номер
      cout<<endl;
    }
    return 0;
}