281. Java 5, 11628 byte, 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);
}
}
Provalo online!
Nota a margine:
- Testato localmente con Java 5. (in modo tale che l'avviso non sia stampato - consultare la scheda debug TIO)
- Non farlo. Mai. Uso. Giava. 1. È più dettagliato di Java in generale.
Questo potrebbe spezzare la catena.
- Il divario (7 giorni e 48 minuti) non è altro che il divario creato da questa risposta , che è di 7 giorni e 1 ora e 25 minuti più tardi rispetto al precedente .
Nuovo record su grande byte! Poiché io (erroneamente?) Uso gli spazi anziché le schede, il bytecount è più grande del necessario. Sulla mia macchina sono 9550 byte. (al momento in cui scrivo questa revisione)
- Prossima sequenza .
- Il codice, nella sua forma attuale, stampa solo i primi 20 termini della sequenza. Tuttavia è facile da cambiare in modo che stia stampando i primi 1000 articoli (cambiando
20
in for (int i = 0; i < 20; ++i)
in 1000
)
Sìì! Questo può calcolare più termini di quelli elencati nella pagina OEIS! (per la prima volta, per una sfida, devo usare Java) a meno che OEIS non abbia più termini da qualche parte ...
Spiegazione veloce
Spiegazione della descrizione della sequenza.
La sequenza richiede il numero di polienoide non piano libero con gruppo di simmetria C 2v , dove:
- polienoide: (modello matematico degli idrocarburi polienici) gli alberi (o in caso degenerato, vertice singolo) possono essere incorporati in un reticolo esagonale.
Ad esempio, considera gli alberi
O O O O (3)
| \ / \
| \ / \
O --- O --- O O --- O O --- O
| \
| (2) \
(1) O O
Il primo non può essere incorporato nel reticolo esagonale, mentre il secondo può essere incorporato. Quel particolare incorporamento è considerato diverso dal terzo albero.
- polienoide non piano: inclusione di alberi in modo tale che esistano due vertici sovrapposti.
(2)
e l' (3)
albero in alto sono planari. Questo, tuttavia, non è planare:
O---O O
/ \
/ \
O O
\ /
\ /
O --- O
(ci sono 7 vertici e 6 bordi)
- polienoide libero: le varianti di un polienoide, ottenibili mediante rotazione e riflessione, sono conteggiate come una sola.
- Gruppo C 2v : i polienoidi vengono contati solo se hanno 2 piani perpendicolari di riflessione e non di più.
Ad esempio, l'unico polienoide con 2 vertici
O --- O
ha 3 piani di riflessione: quello orizzontale -
, quello verticale |
e quello parallelo allo schermo del computer ■
. È troppo.
D'altra parte, questo
O --- O
\
\
O
ha 2 piani di riflessione: /
e ■
.
Spiegazione del metodo
E ora, l'approccio su come contare effettivamente il numero.
Innanzitutto, prendo la formula a(n) = A000063(n + 2) - A000936(n)
(elencata nella pagina OEIS) per scontata. Non ho letto la spiegazione nel documento.
[TODO corregge questa parte]
Ovviamente, contare il planare è più semplice del conteggio non planare. Questo è anche quello che fa il giornale.
I polienoidi geometricamente planari (senza vertici sovrapposti) sono elencati dalla programmazione del computer. In questo modo il numero di polienoidi geometricamente non piani diventa accessibile.
Quindi ... il programma conta il numero di polienoide planare e lo sottrae dal totale.
Poiché l'albero è comunque planare, ovviamente ha il ■
piano di riflessione. Quindi la condizione si riduce a "contare il numero di albero con un asse di riflessione nella sua rappresentazione 2D".
Il modo ingenuo sarebbe generare tutti gli alberi con n
nodi e verificare la corretta simmetria. Tuttavia, poiché vogliamo solo trovare il numero di alberi con un asse di riflessione, possiamo semplicemente generare tutto il possibile mezzo albero su metà, rispecchiarli attraverso l'asse e quindi verificare la corretta simmetria. Inoltre, poiché i polienoidi generati sono alberi (planari), devono toccare l'asse di riflessione esattamente una volta.
La funzione public static Graph[] expand(Graph[] graphs, Point.Predicate fn)
accetta una matrice di grafici, ognuno ha n
nodi e genera una matrice di grafici, ognuno ha n+1
nodi, non uguali tra loro (in fase di traduzione) - in modo tale che il nodo aggiunto deve soddisfare il predicato fn
.
Considera 2 possibili assi di riflessione: uno che attraversa un vertice e coincide con i bordi ( x = 0
) e uno che è la bisettrice perpendicolare di un bordo ( 2x = y
). Possiamo prenderne solo uno perché i grafici generati sono comunque isomorfi.
Quindi, per il primo asse x = 0
, partiamo dal grafico di base costituito da un singolo nodo (1, 0)
(nel caso n
sia dispari) o da due nodi con un bordo tra (1, 0) - (2, 0)
(nel caso n
sia pari), quindi espandiamo i nodi in modo tale y > 0
. Questo è fatto dalla sezione "Tipo di riflessione 1" del programma, quindi per ogni grafico generato, rifletti (specchia) stesso attraverso l'asse X x = 0
( g.reflectSelfX()
), quindi verifica se ha la simmetria corretta.
Tuttavia, si noti che se n
è divisibile per 2, in questo modo abbiamo contato ogni grafico due volte, perché generiamo anche la sua immagine speculare per l'asse 2x = y + 3
.
(nota i 2 arancioni)
Simile per l'asse 2x = y
, se (e solo se) n
è ancora, si parte dal punto (1, 1)
, generare grafici tale che 2*x > y
, e riflettere ciascuno di loro al 2x = y
asse ( g.reflectSelfType2()
), collegare (1, 0)
con (1, 1)
, e controllare se hanno corretto simmetria. Ricorda di dividere anche per 2.