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);
}
}
Probieren Sie es online!
Randnotiz:
- Lokal getestet mit Java 5. (so dass die Warnung nicht gedruckt wird - siehe TIO-Debug-Tab)
- Nicht. Je. Verwenden. Java. 1. Es ist ausführlicher als Java im Allgemeinen.
Dies kann die Kette brechen.
- Die Lücke (7 Tage und 48 Minuten) ist nicht größer als die Lücke, die durch diese Antwort erstellt wurde. Dies sind 7 Tage und 1 Stunde und 25 Minuten später als die vorherige Antwort .
Neuer Rekord auf großem bytecount! Da ich (aus Versehen?) Leerzeichen anstelle von Tabulatoren verwende, ist der Bytecount größer als erforderlich. Auf meinem Rechner sind es 9550 Bytes. (zum Zeitpunkt der Überarbeitung)
- Nächste Sequenz .
- Der Code druckt in seiner aktuellen Form nur die ersten 20 Terme der Sequenz. Es ist jedoch einfach zu ändern, so dass die ersten 1000 Elemente gedruckt werden (durch Ändern des
20
Eingangs for (int i = 0; i < 20; ++i)
zu 1000
).
Yay! Hiermit können mehr Begriffe berechnet werden, als auf der OEIS-Seite aufgeführt sind! (zum ersten Mal, für eine Herausforderung muss ich Java verwenden) es sei denn, OEIS hat irgendwo mehr Begriffe ...
Schnelle Erklärung
Erläuterung der Sequenzbeschreibung.
Die Sequenz fragt nach der Anzahl der freien nichtplanaren Polyomagneten mit der Symmetriegruppe C 2v , wobei:
- Polyenoid: (mathematisches Modell von Polyenkohlenwasserstoffen) Bäume (oder im entarteten Fall ein einzelner Scheitelpunkt) können in ein hexagonales Gitter eingebettet werden.
Betrachten Sie zum Beispiel die Bäume
O O O O (3)
| \ / \
| \ / \
O --- O --- O O --- O O --- O
| \
| (2) \
(1) O O
Das erste kann nicht in das hexagonale Gitter eingebettet werden, das zweite nicht. Diese spezielle Einbettung unterscheidet sich vom dritten Baum.
- nichtplanares Polyenoid: Einbetten von Bäumen, sodass zwei überlappende Scheitelpunkte vorhanden sind.
(2)
und (3)
Baum oben sind planar. Dieser ist jedoch nicht planar:
O---O O
/ \
/ \
O O
\ /
\ /
O --- O
(Es gibt 7 Ecken und 6 Kanten)
- Freier Hubmagnet: Varianten eines Hubmagneten, die durch Rotation und Reflexion erhalten werden können, werden als einer gezählt.
- C 2v- Gruppe: Die Polyomagneten werden nur gezählt, wenn sie 2 senkrechte Reflexionsebenen haben und nicht mehr.
Zum Beispiel das einzige Magnetventil mit 2 Eckpunkten
O --- O
hat 3 Reflexionsebenen: die horizontale -
, die vertikale |
und die parallel zum Computerbildschirm verlaufende ■
. Das ist zu viel.
Auf der anderen Seite dieser
O --- O
\
\
O
hat 2 Reflexionsebenen: /
und ■
.
Erläuterung der Methode
Und jetzt der Ansatz, wie man die Anzahl tatsächlich zählt.
Zunächst nehme ich die a(n) = A000063(n + 2) - A000936(n)
auf der OEIS-Seite aufgeführte Formel als selbstverständlich an. Ich habe die Erklärung in der Zeitung nicht gelesen.
[TODO fix this part]
Natürlich ist das Zählen von Planaren einfacher als das Zählen von Nichtplanaren. Das macht auch die Zeitung.
Geometrisch planare Polyenoide (ohne überlappende Eckpunkte) werden durch Computerprogrammierung aufgezählt. Dadurch wird die Anzahl der geometrisch nichtplanaren Polyenoide zugänglich.
Also ... zählt das Programm die Anzahl der planaren Magnetspulen und subtrahiert sie von der Summe.
Da der Baum sowieso eben ist, hat er offensichtlich die ■
Reflexionsebene. Die Bedingung läuft also auf "Anzahl der Bäume mit einer Reflektionsachse in der 2D-Darstellung zählen" hinaus.
Der naive Weg wäre, alle Bäume mit n
Knoten zu generieren und auf korrekte Symmetrie zu prüfen. Da wir jedoch nur die Anzahl der Bäume mit einer Reflexionsachse ermitteln möchten, können wir einfach alle möglichen Halbbäume auf einer Hälfte generieren, sie durch die Achse spiegeln und dann auf korrekte Symmetrie prüfen. Da es sich bei den erzeugten Polyenoiden um (planare) Bäume handelt, muss sie die Reflexionsachse genau einmal berühren.
Die Funktion public static Graph[] expand(Graph[] graphs, Point.Predicate fn)
nimmt ein Array von Graphen auf, jeder hat n
Knoten und gibt ein Array von Graphen aus, jeder hat n+1
Knoten, die nicht gleich sind (unter Übersetzung) - so dass der hinzugefügte Knoten das Prädikat erfüllen muss fn
.
Betrachten Sie zwei mögliche Reflexionsachsen: Eine, die durch einen Scheitelpunkt verläuft und mit Kanten zusammenfällt ( x = 0
), und eine, die die senkrechte Winkelhalbierende einer Kante ist ( 2x = y
). Wir können nur eine davon verwenden, da die generierten Grafiken ohnehin isomorph sind.
Für die erste Achse x = 0
beginnen wir also mit dem Basisgraphen, der aus einem einzelnen Knoten (1, 0)
(falls n
ungerade ist) oder zwei Knoten mit einer Kante zwischen (1, 0) - (2, 0)
(falls gerade n
ist) besteht, und erweitern dann die Knoten so, dass y > 0
. Dies wird im Abschnitt "Reflection type 1" des Programms durchgeführt. Anschließend spiegeln Sie sich für jedes erzeugte Diagramm durch die X-Achse x = 0
( g.reflectSelfX()
) und überprüfen, ob es die richtige Symmetrie aufweist.
Beachten Sie jedoch, dass wenn n
durch 2 teilbar ist, wir auf diese Weise jeden Graphen zweimal gezählt haben, da wir auch sein Spiegelbild durch die Achse erzeugen 2x = y + 3
.
(Beachten Sie die 2 orangefarbenen)
Ähnliches gilt für die Achse 2x = y
, wenn (und nur dann) n
auch ist, beginnen wir von dem Punkt (1, 1)
, erzeugen Graphen , so dass 2*x > y
, und beziehen sich jeder von ihnen über die 2x = y
Achse ( g.reflectSelfType2()
), verbinden (1, 0)
mit (1, 1)
, und überprüfen , ob sie korrekt Symmetrie haben. Denken Sie auch daran, durch 2 zu teilen.