281. Java 5, 11628 bytes, A000947
// package oeis_challenge;
import java.util.*;
import java.lang.*;
class Main {
// static void assert(boolean cond) {
// if (!cond)
// throw new Error("Assertion failed!");
// }
/* Use the formula a(n) = A000063(n + 2) - A000936(n).
It's unfair that I use the formula of "number of free polyenoid with n
nodes and symmetry point group C_{2v}" (formula listed in A000063)
without understanding why it's true...
*/
static int catalan(int x) {
int ans = 1;
for (int i = 1; i <= x; ++i)
ans = ans * (2*x+1-i) / i;
return ans / -~x;
}
static int A63(int n) {
int ans = catalan(n/2 - 1);
if (n%4 == 0) ans -= catalan(n/4 - 1);
if (n%6 == 0) ans -= catalan(n/6 - 1);
return ans;
}
static class Point implements Comparable<Point> {
final int x, y;
Point(int _x, int _y) {
x = _x; y = _y;
}
/// @return true if this is a point, false otherwise (this is a vector)
public boolean isPoint() {
return (x + y) % 3 != 0;
}
/// Translate this point by a vector.
public Point add(Point p) {
assert(this.isPoint() && ! p.isPoint());
return new Point(x + p.x, y + p.y);
}
/// Reflect this point along x-axis.
public Point reflectX() {
return new Point(x - y, -y);
}
/// Rotate this point 60 degrees counter-clockwise.
public Point rot60() {
return new Point(x - y, x);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point p = (Point) o;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return 21521 * (3491 + x) + y;
}
public String toString() {
// return String.format("(%d, %d)", x, y);
return String.format("setxy %d %d", x * 50 - y * 25, y * 40);
}
public int compareTo(Point p) {
int a = Integer.valueOf(x).compareTo(p.x);
if (a != 0) return a;
return Integer.valueOf(y).compareTo(p.y);
}
/// Helper class.
static interface Predicate {
abstract boolean test(Point p);
}
static abstract class UnaryFunction {
abstract Point apply(Point p);
}
}
static class Edge implements Comparable<Edge> {
final Point a, b; // guarantee a < b
Edge(Point x, Point y) {
assert x != y;
if (x.compareTo(y) > 0) { // y < x
a = y; b = x;
} else {
a = x; b = y;
}
}
public int compareTo(Edge e) {
int x = a.compareTo(e.a);
if (x != 0) return x;
return b.compareTo(e.b);
}
}
/// A graph consists of multiple {@code Point}s.
static class Graph {
private HashMap<Point, Point> points;
public Graph() {
points = new HashMap<Point, Point>();
}
public Graph(Graph g) {
points = new HashMap<Point, Point>(g.points);
}
public void add(Point p, Point root) {
assert(p.isPoint());
assert(root.isPoint());
assert(p == root || points.containsKey(root));
points.put(p, root);
}
public Graph map(Point.UnaryFunction fn) {
Graph result = new Graph();
for (Map.Entry<Point, Point> pq : points.entrySet()) {
Point p = pq.getKey(), q = pq.getValue();
assert(p.isPoint()) : p;
assert(q.isPoint()) : q;
p = fn.apply(p); assert(p.isPoint()) : p;
q = fn.apply(q); assert(q.isPoint()) : q;
result.points.put(p, q);
}
return result;
}
public Graph reflectX() {
return this.map(new Point.UnaryFunction() {
public Point apply(Point p) {
return p.reflectX();
}
});
}
public Graph rot60() {
return this.map(new Point.UnaryFunction() {
public Point apply(Point p) {
return p.rot60();
}
});
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (o.getClass() != getClass()) return false;
Graph g = (Graph) o;
return points.equals(g.points);
}
@Override
public int hashCode() {
return points.hashCode();
}
Graph[] expand(Point.Predicate fn) {
List<Graph> result = new ArrayList<Graph>();
for (Point p : points.keySet()) {
int[] deltaX = new int[] { -1, 0, 1, 1, 0, -1};
int[] deltaY = new int[] { 0, 1, 1, 0, -1, -1};
for (int i = 6; i --> 0;) {
Point p1 = new Point(p.x + deltaX[i], p.y + deltaY[i]);
if (points.containsKey(p1) || !fn.test(p1)
|| !p1.isPoint()) continue;
Graph g = new Graph(this);
g.add(p1, p);
result.add(g);
}
}
return result.toArray(new Graph[0]);
}
public static Graph[] expand(Graph[] graphs, Point.Predicate fn) {
Set<Graph> result = new HashSet<Graph>();
for (Graph g0 : graphs) {
Graph[] g = g0.expand(fn);
for (Graph g1 : g) {
if (result.contains(g1)) continue;
result.add(g1);
}
}
return result.toArray(new Graph[0]);
}
private Edge[] edges() {
List<Edge> result = new ArrayList<Edge>();
for (Map.Entry<Point, Point> pq : points.entrySet()) {
Point p = pq.getKey(), q = pq.getValue();
if (p.equals(q)) continue;
result.add(new Edge(p, q));
}
return result.toArray(new Edge[0]);
}
/**
* Check if two graphs are isomorphic... under translation.
* @return {@code true} if {@code this} is isomorphic
* under translation, {@code false} otherwise.
*/
public boolean isomorphic(Graph g) {
if (points.size() != g.points.size()) return false;
Edge[] a = this.edges();
Edge[] b = g.edges();
Arrays.sort(a);
Arrays.sort(b);
// for (Edge e : b)
// System.err.println(e.a + " - " + e.b);
// System.err.println("------- >><< ");
assert (a.length > 0);
assert (a.length == b.length);
int a_bx = a[0].a.x - b[0].a.x, a_by = a[0].a.y - b[0].a.y;
for (int i = 0; i < a.length; ++i) {
if (a_bx != a[i].a.x - b[i].a.x ||
a_by != a[i].a.y - b[i].a.y) return false;
if (a_bx != a[i].b.x - b[i].b.x ||
a_by != a[i].b.y - b[i].b.y) return false;
}
return true;
}
// C_{2v}.
public boolean correctSymmetry() {
Graph[] graphs = new Graph[6];
graphs[0] = this.reflectX();
for (int i = 1; i < 6; ++i) graphs[i] = graphs[i-1].rot60();
assert(graphs[5].rot60().isomorphic(graphs[0]));
int count = 0;
for (Graph g : graphs) {
if (this.isomorphic(g)) ++count;
// if (count >= 2) {
// return false;
// }
}
// if (count > 1) System.err.format("too much: %d%n", count);
assert(count > 0);
return count == 1; // which is, basically, true
}
public void reflectSelfType2() {
Graph g = this.map(new Point.UnaryFunction() {
public Point apply(Point p) {
return new Point(p.y - p.x, p.y);
}
});
Point p = new Point(1, 1);
assert (p.equals(points.get(p)));
points.putAll(g.points);
assert (p.equals(points.get(p)));
Point q = new Point(0, 1);
assert (q.equals(points.get(q)));
points.put(p, q);
}
public void reflectSelfX() {
Graph g = this.reflectX();
points.putAll(g.points); // duplicates doesn't matter
}
}
static int A936(int n) {
// if (true) return (new int[]{0, 0, 0, 1, 1, 2, 4, 4, 12, 10, 29, 27, 88, 76, 247, 217, 722, 638, 2134, 1901, 6413})[n];
// some unreachable codes here for testing.
int ans = 0;
if (n % 2 == 0) { // reflection type 2. (through line 2x == y)
Graph[] graphs = new Graph[1];
graphs[0] = new Graph();
Point p = new Point(1, 1);
graphs[0].add(p, p);
for (int i = n / 2 - 1; i --> 0;)
graphs = Graph.expand(graphs, new Point.Predicate() {
public boolean test(Point p) {
return 2*p.x > p.y;
}
});
int count = 0;
for (Graph g : graphs) {
g.reflectSelfType2();
if (g.correctSymmetry()) {
++count;
// for (Edge e : g.edges())
// System.err.println(e.a + " - " + e.b);
// System.err.println("------*");
}
// else System.err.println("Failed");
}
assert (count%2 == 0);
// System.err.println("A936(" + n + ") count = " + count + " -> " + (count/2));
ans += count / 2;
}
// Reflection type 1. (reflectX)
Graph[] graphs = new Graph[1];
graphs[0] = new Graph();
Point p = new Point(1, 0);
graphs[0].add(p, p);
if (n % 2 == 0) graphs[0].add(new Point(2, 0), p);
for (int i = (n-1) / 2; i --> 0;)
graphs = Graph.expand(graphs, new Point.Predicate() {
public boolean test(Point p) {
return p.y > 0;
}
});
int count = 0;
for (Graph g : graphs) {
g.reflectSelfX();
if (g.correctSymmetry()) {
++count;
// for (Edge e : g.edges())
// System.err.printf(
// "pu %s pd %s\n"
// // "%s - %s%n"
// , e.a, e.b);
// System.err.println("-------/");
}
// else System.err.println("Failed");
}
if(n % 2 == 0) {
assert(count % 2 == 0);
count /= 2;
}
ans += count;
// System.err.println("A936(" + n + ") = " + ans);
return ans;
}
public static void main(String[] args) {
// Probably
if (! "1.5.0_22".equals(System.getProperty("java.version"))) {
System.err.println("Warning: Java version is not 1.5.0_22");
}
// A936(6);
for (int i = 0; i < 20; ++i)
System.out.println(i + " | " + (A63(i+9) - A936(i+7)));
//A936(i+2);
}
}
Pruébalo en línea!
Nota al margen:
- Probado localmente con Java 5. (de modo que la advertencia no se imprima; consulte la pestaña de depuración de TIO)
- No lo hagas Siempre. Utilizar. Java. 1. Es más detallado que Java en general.
Esto puede romper la cadena.
- La brecha (7 días y 48 minutos) no es más que la brecha creada por esta respuesta , que es de 7 días y 1 hora 25 minutos más tarde que la anterior .
Nuevo récord en gran bytecount! Debido a que (¿equivocadamente?) Uso espacios en lugar de pestañas, el bytecount es más grande de lo necesario. En mi máquina son 9550 bytes. (al momento de escribir esta revisión)
- Secuencia siguiente .
- El código, en su forma actual, solo imprime los primeros 20 términos de la secuencia. Sin embargo, es fácil de cambiar para que se imprime primeros 1000 elementos (por el cambio de la
20
de for (int i = 0; i < 20; ++i)
a 1000
)
¡Hurra! ¡Esto puede calcular más términos que los que figuran en la página OEIS! (Por primera vez, para un desafío, necesito usar Java) a menos que OEIS tenga más términos en alguna parte ...
Explicacion rapida
Explicación de la descripción de la secuencia.
La secuencia solicita el número de polienoides no planos libres con el grupo de simetría C 2v , donde:
- polienoide: (modelo matemático de hidrocarburos de polieno) los árboles (o en caso degenerado, vértice único) con pueden ser incrustados en una red hexagonal.
Por ejemplo, considera los árboles
O O O O (3)
| \ / \
| \ / \
O --- O --- O O --- O O --- O
| \
| (2) \
(1) O O
El primero no puede incrustarse en la red hexagonal, mientras que el segundo sí. Esa inclusión particular se considera diferente del tercer árbol.
- polienoide no plano: incrustación de árboles de modo que existan dos vértices superpuestos.
(2)
y el (3)
árbol de arriba son planos. Este, sin embargo, no es plano:
O---O O
/ \
/ \
O O
\ /
\ /
O --- O
(hay 7 vértices y 6 aristas)
- polienoide libre: las variantes de un polienoide, que se pueden obtener por rotación y reflexión, se cuentan como una.
- Grupo C 2v : los polienoides solo se cuentan si tienen 2 planos perpendiculares de reflexión, y no más.
Por ejemplo, el único polienoide con 2 vértices.
O --- O
tiene 3 planos de reflexión: el horizontal -
, el vertical |
y el paralelo a la pantalla de la computadora ■
. Eso es demasiado.
Por otro lado, este
O --- O
\
\
O
Tiene 2 planos de reflexión: /
y ■
.
Explicación del método.
Y ahora, el enfoque sobre cómo contar realmente el número.
Primero, doy a(n) = A000063(n + 2) - A000936(n)
por sentado la fórmula (que figura en la página OEIS). No leí la explicación en el periódico.
[TODO arregla esta parte]
Por supuesto, contar plano es más fácil que contar no plano. Eso es lo que hace el periódico también.
Los polienoides geométricamente planos (sin vértices superpuestos) se enumeran mediante programación informática. Por lo tanto, los números de polienoides geométricamente no planos se vuelven accesibles.
Entonces ... el programa cuenta el número de polienoides planos y lo resta del total.
Como el árbol es plano de todos modos, obviamente tiene el ■
plano de reflexión. Por lo tanto, la condición se reduce a "contar el número de árboles con un eje de reflexión en su representación 2D".
La forma ingenua sería generar todos los árboles con n
nodos y verificar la simetría correcta. Sin embargo, debido a que solo queremos encontrar el número de árboles con un eje de reflexión, podemos generar todos los medios árboles posibles en una mitad, reflejarlos a través del eje y luego verificar la simetría correcta. Además, debido a que los polienoides generados son árboles (planos), debe tocar el eje de reflexión exactamente una vez.
La función public static Graph[] expand(Graph[] graphs, Point.Predicate fn)
toma una matriz de gráficos, cada uno tiene n
nodos y genera una matriz de gráficos, cada uno tiene n+1
nodos, no iguales entre sí (bajo traducción), de modo que el nodo agregado debe satisfacer el predicado fn
.
Considere 2 posibles ejes de reflexión: uno que atraviesa un vértice y coincide con los bordes ( x = 0
), y otro que es la bisectriz perpendicular de un borde ( 2x = y
). Podemos tomar solo uno de ellos porque los gráficos generados son isomórficos, de todos modos.
Entonces, para el primer eje x = 0
, comenzamos desde el gráfico base que consiste en un solo nodo (1, 0)
(en caso de que n
sea impar) o dos nodos con un borde en medio (1, 0) - (2, 0)
(en caso de que n
sea par), y luego expandimos los nodos de tal manera y > 0
. Esto se hace mediante la sección "Reflexión tipo 1" del programa, y luego para cada gráfico generado, se refleja (refleja) a través del eje X x = 0
( g.reflectSelfX()
), y luego verifica si tiene la simetría correcta.
Sin embargo, tenga en cuenta que si n
es divisible por 2, de esta manera contamos cada gráfico dos veces, porque también generamos su imagen especular por el eje 2x = y + 3
.
(nota las 2 naranjas)
Similar para el eje 2x = y
, si (y sólo si) n
es par, partimos del punto (1, 1)
, generar gráficos de tal manera que 2*x > y
, y reflejar cada uno de ellos sobre el 2x = y
eje ( g.reflectSelfType2()
), conectar (1, 0)
con (1, 1)
, y comprobar si tienen simetría correcta. Recuerde dividir entre 2 también.