Como resolver o problema do transporte público lotado em São Paulo

Por Anônimo bêbado falando sozinho num 917H lotado, sexta-feira às 18:30

Precisa de um Bin Laden. É o único jeito. Tem que explodir tudo. Explode essa gente que acaba o problema.

Não… não precisa. Tem só que devolver. Devolve todo mundo. Quem é de Minas, quem é do Nordeste. Só fica aqui quem nasceu aqui. Não ia sobrar nem um milhão.

Devolve tudo! Faz exame de DNA pra confirmar quem nasceu aqui mesmo. Não vai sobrar nem um milhão.

Se eu fosse vereador, ou prefeito, ou alguém que pode mandar em alguma coisa, devolvia todo mundo!

Epílogo

“Ah, aleluia! Tinha um homem falando besteira aqui. Pior que ônibus lotado é essa gente gritando.” (mulher mal-humorada ao telefone, após o autor do texto descer)

Spivak sobre definições e teoremas

“The reader probably suspects that the modern Stokes’ Theorem is at least as difficult as the classical theorems derived from it. On the contrary, it is a very simple consequence of yet another version of Stokes’ Theorem; this very abstract version is the final and main result of Chapter 4. It is entirely reasonable to suppose that the difficulties so far avoided must be hidden here. Yet the proof of this theorem is, in the mathematician’s sense, an utter triviality — a straight-forward computation. On the other hand, even the statement of this triviality cannot be understood without a horde of difficult definitions from Chapter 4. There are good reasons why the theorems should all be easy and the definitions hard. As the evolution of Stokes’ Theorem revealed, a single simple principle can masquerade as several difficult results; the proofs of many theorems involve merely stripping away the disguise. The definitions, on the other hand, serve a twofold purpose: they are rigorous replacements for vague notions, and machinery for elegant proofs.”

Michael Spivak, prefácio de “Calculus on Manifolds”.

Parceria USP-Microsoft?

A notícia da visita de Steve Ballmer à USP me preocupou, em especial seu último parágrafo:

Para Massambani, a Microsoft pode acelerar os processos e alavancar os projetos já existentes no desenvolvimento de processos de criatividade na área digital, laboratório de criatividade e inovação. “A Microsoft pode ajudar a USP em projetos relacionados com infraestrutura, suporte, educação, sociocultural, servindo como popularização da ciência, inclusão social e digital. As duas podem cooperar para o desenvolvimento de pesquisas, capital intelectual e responsabilidade social”, considera.

A Microsoft tem esse costume (que o Sérgio Amadeu chama de “prática de traficante”) de oferecer a governos, universidades e mesmo a professores individualmente dinheiro, laboratórios, computadores e licenças do seu sistema operacional com esse discurso de inclusão digital e educação; criar dependentes.

Inclusão digital e social com um software que custa mais que o salário mínimo não é inclusão. Educação sem acesso ao código não é educação; é como ensinar a fórmula da soma de progressão aritmética sem permitir que o estudante saiba de onde ela vem ou criar cozinheiros ensinando a colocar lasanha da Sadia no micro-ondas. Popularização da ciência? Que ciência? A única popularização que vejo é da marca de uma empresa internacionalmente conhecida por sua política imperialista e por monopólio.

Desenvolvimento de pesquisa pra terceiros é capital intelectual desperdiçado. Por isso, essa parceria é o que eu chamo de irresponsabilidade social. É um erro uma universidade pública abrir as portas pra esse tipo de negócio que deseduca, desvirtua e vicia a sociedade.

Se a Microsoft quer tanto assim um mundo melhor e leva a sério seu próprio discurso de querer ver a população de São Paulo incluída digitalmente, sugiro que doe dinheiro aos telecentros paulistas sem esperar nada em troca.

Já tinha escrito um resumo disso no Twitter, mas achei conveniente repetir aqui.

Boa nova

Resolvi fazer Ciência da Computação há muito tempo. Faz tanto tempo que eu não lembro quando foi, mas acho que eu tinha uns oito anos. Minha única certeza é que eu não fazia ideia do que era o curso (mas isso não importa — hoje acho que escolhi estudar uma das coisas mais legais que existem).

O tempo passou e cogitei fazer outras faculdades, mas nunca seriamente. Começou o 3º ano do Ensino Médio e comparei os currículos de UFSC, UNICAMP, ICMC-USP e IME-USP pra decidir que curso escolher. Ordenei-os (por motivos teóricos) da seguinte forma:

  1. IME-USP
  2. ICMC-USP
  3. IC-UNICAMP (engenharia)
  4. UFSC

Desde lá minha meta foi entrar no lugar onde hoje, felizmente, estou. Mas não foi fácil.

Passei o último ano do Ensino Médio namorando estudando, li os resumos dos livros exigidos e quando chegou novembro… não passei na primeira fase do vestibular da Fuvest.

(Felizmente passei na UFSC e vivi um ano sensacional. Morava do lado da Universidade, fiz grandes amigos, conheci professores do mais alto nível, me classifiquei pra final mundial da Maratona de Programação e aprendi mais Matemática do que em toda a vida. Mas nem todos têm a mesma sorte.)

O vestibular da USP usa um terrível sistema baseado em carreiras.

Def. Carreiras são conjuntos disjuntos não-vazios de cursos universitários que em geral tem algo em comum (e.g., uma carreira pode ter Engenharia de Produção e Ciência da Computação porque ambos são cursos pra seres humanos — não sei se poderia haver alguma outra razão mais específica, sem ser através da Lei dos Cinco, mas creio que não).

No sistema da USP o candidato escolhe uma carreira, cursos que gostaria de fazer nessa carreira e sua ordem de preferência.

Passam pra segunda fase do vestibular três vezes o número de vagas disponíveis na carreira. Depois da segunda fase, os candidatos são ordenados de acordo com a nota da segunda fase e roda-se um algoritmo assim:

for (int pos = 0; tem_vagas_sobrando() && pos < n; pos++) {
    for (int opcao = 0; opcao < 4; opcao++) {
        if (tem_vagas_no_curso(pessoa[pos].opcao[opcao])) {
            da_vaga(pessoa[pos], pessoa[pos].opcao[opcao]);
            break;
        }
    }
}

Estava com sono e dificuldade de pensar quando postei. Outra hora tento passar pra uma língua menos nerd.

São os institutos que decidem em que carreira seus cursos vão entrar e o negócio fica uma bagunça. A maioria das carreiras têm cursos iguais com diferença apenas de período (diurno e noturno), mas há carreiras de institutos inteiros (a FEA, por exemplo, tem apenas uma carreira onde coloca Economia [diurno e noturno], Administração [diurno e noturno], Ciências contábeis [diurno e noturno] e Bacharelado em Ciências Atuariais), de cursos iguais em diferentes campi (na carreira de Direito, por exemplo, o candidato pode escolher entre o Largo São Francisco e Ribeirão Preto) e, por fim, carreiras como a minha: Engenharia na Escola Politécnica e Computação, que oferece (versão Fuvest 2010):

  • Engenharia Civil e Engenharia Ambiental (poli)
  • Engenharia Elétrica (ênfases: Automação e controle, energia e automação elétricas, sistemas eletrônicos, telecomunicações) (poli)
  • Engenharia Mecânica e Engenharia Naval (poli)
  • Engenharia Química, Engenharia Metalúrgica, Engenharia de Materiais, Engenharia de Minas e Engenharia de Petróleo (poli)
  • Engenharia de Computação e Engenharia Elétrica (ênfase Computação) (poli)
  • Engenharia Mecânica - Automação e Sistemas (Mecatrônica) (poli)
  • Engenharia de Produção (poli)
  • Bacharelado em Ciência da Computação (IME!)

Reza a lenda que essa era uma carreira que tinha todos os cursos que classificam como Exatas (uma classificação ridícula, na minha opinião) e todos eles foram saindo, até que no meu ano sobraram só as engenharias da Poli e o BCC.

(E eu prefiro acreditar nisso porque me doeria acreditar o contrário – aceitar que em certo momento da História algum idiota professor decidiu que Ciência da Computação tem mais a ver com Engenharia Ambiental do que com Matemática.)

Agora veja o problema: Em um ano aqui, aprendi que trabalhar em bancos está na moda em São Paulo. Como se formar em engenharia na Escola Politécnica é garantia desse nobre emprego, fazem um monte de cursinhos (e turmas especiais neles) voltados a destruir o cérebro das ensinar crianças (o link é bom; clique!) pra jihad passar na Fuvest. O resultado é que um catarinense que quer entrar no Bacharelado em Ciência da Computação não consegue nem passar da primeira fase do concurso. Se passa pra segunda fase, ainda assim precisa competir com estudantes que colocaram o BCC na quarta opção para não decepcionar os pais e seu ego caso não passem nas três engenharias que desejam.

E não para por aí.

O BCC abre 50 vagas por ano e neste ano matricularam-se 31 calouros. Os alunos da turma (para a qual dou monitoria da disciplina Introdução à Computação) me contaram que tem 26 pessoas indo assistir as aulas. Enquanto há jovens no Brasil inteiro querendo entrar neste curso, que considero um dos melhores (se não o melhor) do país, a sala da turma de 2010 está com metade de sua capacidade porque gente que queria fazer engenharia marcou a opção do BCC e não fez a matrícula.

A solução imediata é óbvia: tirar o Bacharelado em Ciência da Computação da carreira da Escola Politécnica.

Felizmente, não sou o único que penso isso. Então, após todo esse preâmbulo, informo em primeira mão: a Congregação do Instituto de Matemática e Estatística, em sessão ordinária realizada hoje (29/04) da qual tive o enorme prazer de participar, aprovou por unanimidade essa decisão, que já havia sido aprovada (também por unanimidade) dentro do Departamento de Computação.

Será criada nesse ano na Fuvest uma carreira chamada “Bacharelado em Ciência da Computação”, que a princípio terá 50 vagas, mas para a qual será convidado o Bacharelado em Ciência da Computação do ICMC-USP (São Carlos).

A decisão é fantástica e será fundamental pra vida de diversos futuros estudantes desta faculdade. Já estou ansioso pelo ano que vem…

Coordenadas homogêneas

Conheci as coordenadas homogêneas por acaso. Era 2004, ganhei a modalidade iniciação da Olimpíada Brasileira de Informática e passei o inverno estudando C, acho que por dois motivos: interesse pelos problemas da modalidade programação e desejo de aproveitar bem o curso que os medalhistas fazem na UNICAMP; ou talvez fosse apenas falta do que fazer ou curiosidade mesmo. Não importa.

Confundo os cursos de 2004, 2005 e 2006, mas lembro que no primeiro aprendi com um monitor sobre recursão, representação de grafos e busca em profundidade. Lembro também que foi em 2004 que conheci o CLRS (livro que comprei alguns meses depois e tenho na cabeceira até hoje). Esse post, porém, é sobre um acontecimento mais aleatório relacionado a esse curso e, ouso afirmar, digno de figurar n’O Andar do Bêbado do Mlodinow.

O pessoal da modalidade programação ia tirar cópias de um livro do Stolfi e do Rezende, chamado “Fundamentos de Geometria Computacional” (dá pra baixar aqui). Nem sabia do que estavam tirando xerox, mas vi que era barato e que todos os mais velhos estavam querendo, então também pedi.

Folheei o livro, não entendi nada, deixei num canto e voltei a abrir alguns anos depois, não lembro exatamente quando. E foi aí que fez-se a luz. A primeira parte é sobre coordenadas homogêneas.

Coordenadas homogêneas? A ideia parece simples (até demais) pra ser poderosa. Basicamente você representa uma coordenada em (X,Y)R2(X, Y) \in \mathbb{R}^2 com uma tripla ordenada [w,x,y][w, x, y] tal que x=Xwx = \frac{X}{w} e y=Ywy = \frac{Y}{w}. A reta tem a mesma representação.

struct ponto {
	int w, x, y;
};

typedef ponto reta;

E o que isso tem demais? Você deixa de precisar de pontos flutuantes pra maioria das operações geométricas; ganha pontos no infinito (eles são a interseção entre retas paralelas!) que permitem fazer fórmulas sem preocupação com casos especiais, usar a mesma fórmula pra determinar uma reta gerada por dois pontos ou por um ponto e um vetor etc.; tem representações iguais (e dualidade) entre pontos e retas (e.g. interseção entre retas p e q = reta determinada por pontos p e q); e mais um monte de outras coisas.

Uau! Como eu não ouvi falar disso antes? Eu não sei a razão, mas, embora tenham várias vantagens, as coordenadas homogêneas não são muito populares na universidade, nem entre os maratonistas. No entanto, são usadas em projetos grandes que usam muita geometria (e.g. OpenGL).

Quero código! Vou mostrar como resolvi o problema Symmetry (2002 TopCoder Inv Round 4 – Division I, Level Three).

O enunciado é simples: Dados n (2n2002 \leq n \leq 200) pontos inteiros (com coordenadas de -10000 a 10000) determinar quantas linhas de simetria existem entre eles.

Dá pra fazer em O(n3)O(n^3) com a seguinte ideia:

  1. Crie uma árvore binária balanceada indexada por retas. (em C++, map <reta,int>)
  2. Para cada par de pontos, determine a reta de simetria entre eles e adicione 2 a essa reta na árvore binária. (O(n2logn)O(n^2 log n))
  3. Para cada reta na árvore binária, adicione 1 para cada ponto que pertence a essa reta. (O(n3)O(n^3))
  4. É fácil ver que a reta é uma reta de simetria do conjunto de pontos se e somente se seu valor na árvore binária for nn.

O problema geométrico está no segundo passo: determinar a reta de simetria entre dois pontos. Sejam esses pontos p e q. É preciso:

  1. Determinar o ponto médio entre p e q.
  2. Determinar a reta que passa por p e q (o enunciado garante que p != q).
  3. Determinar uma reta (ou um vetor) perpendicular à reta do passo acima.
  4. Determinar a reta que passa pelo ponto médio e tem a direção do vetor perpendicular do passo 3.

Determinar o ponto médio sem usar ponto flutuante seria trivial de qualquer forma (basta multiplicar todas as coordenadas por dois), mas com coordenadas homogêneas isso é desnecessário. É fácil ver que o ponto médio mm entre p=[w0,x0,y0]p = [w_0, x_0, y_0] e q=[w1,x1,y1]q = [w_1, x_1, y_1] é:

m=[2w0w1,w1x0+w0x1,w1y0+w0q1]m = [ 2 w_0 w_1 , w_1 x_0 + w_0 x_1 , w_1 y_0 + w_0 q_1 ]
ponto ponto_medio(ponto p, ponto q) {
	return (ponto) { 2*p.w*q.w, q.w*p.x+q.x*p.w, q.w*p.y+q.y*p.w };
}

Três pontos [w0,x0,y0][w_0, x_0, y_0], [w1,x1,y1][w_1, x_1, y_1], [w2,x2,y2][w_2, x_2, y_2] são colineares se

w0x0y0w1x1y1w2x2y2=0\left| \begin{array}{ccc} w_0 & x_0 & y_0 \\ w_1 & x_1 & y_1 \\ w_2 & x_2 & y_2 \end{array} \right| = 0

, o que nos permite concluir que a reta r=W,X,Yr = \langle W, X, Y \rangle que passa por p=[w0,x0,y0]p = [ w_0, x_0, y_0 ] e q=[w1,x1,y1]q = [ w_1, x_1, y_1 ] é:

r=+x0y1y0x1,w0y1+w1y0,w0x1w1x0r = \langle +x_0 y_1 - y_0 x_1, -w_0 y_1 + w_1 y_0, w_0 x_1 - w_1 x_0\rangle

Logo:

ponto reta_determinada_por(ponto p, ponto q) {
	return (reta) { +p.x*q.y-q.x*p.y, -p.w*q.y+q.w*p.y, +p.w*q.x-q.w*p.x };
}

(um ponto [w,x,y][w, x, y] pertence a rr se Ww+Xx+Yy=0Ww + Xx + Yy = 0)

int ponto_na_reta(ponto p, reta r) {
	return p.w*r.w + p.x*r.x + p.y*r.y == 0;
}

Agora a parte mais legal: a fórmula para determinar uma reta que passa por dois pontos funciona com pontos no infinito (pensemos em pontos no infinito como vetores, porque eles tem direção mas tem w=0w = 0): o resultado é a reta determinada por um ponto e uma direção. O vetor perpendicular à reta W,X,Y\langle W, X, Y \rangle é [0,X,Y][ 0, X, Y ].

ponto ponto_infinito_na_reta_perpendicular(reta r) {
	return (reta) { 0, r.x, r.y };
}

E isso é tudo. Agora basta criar uma representação única da reta pra guardar na árvore binária.

reta reformata_reta(reta r) {
	if (r.w < 0) {
		r.w = -r.w;
		r.x = -r.x;
		r.y = -r.y;
	} else if (r.w == 0) {
		if (r.x < 0) {
			r.x = -r.x;
			r.y = -r.y;
		} else if (r.x == 0 && r.y < 0) {
			r.y = -r.y;
		}
	}
	int d = gcd(r.w, gcd(abs(r.x), abs(r.y)));
	return (reta) { r.w/d, r.x/d, r.y/d };
}

Usando essas funções, primeiro e segundo passos da solução são:

map <reta,int> M;
for (int i = 0; i < n; i++) {
	for (int j = i+1; j < n; j++) {
		reta s = reformata_reta(reta_determinada_por(P[i], P[j]));
		ponto pm = ponto_medio(P[i], P[j]);
		ponto dir = ponto_infinito_na_reta_perpendicular(s);
		reta r = reformata_reta(reta_determinada_por(pm, dir));
		if (M.find(s) == M.end())
			M[s] = 0;
		if (M.find(r) == M.end())
			M[r] = 0;
		M[r]+= 2;
	}
}

Terceiro e o quarto são:

int output = 0;
for (map <reta,int>::iterator i = M.begin(); i != M.end(); i++) {
	for (int j = 0; j < n; j++)
		if (ponto_na_reta(P[j], i->first))
			i->second++;
	if (i->second == n)
		output++;
}

O código completo ficou com umas 90 linhas com comentários e linhas em branco e foi aceito na primeira submissão (ok, na verdade na segunda, mas não foi devido à geometria muito menos à precisão): symmetry.cpp (infelizmente, esse arquivo foi perdido com o tempo). Não é lindo?

© 2005–2020 Tiago Madeira