281. Java 5, 11628 octets, 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);
}
}
Essayez-le en ligne!
Note de côté:
- Testé localement avec Java 5. (de sorte que l'avertissement ne soit pas imprimé - voir l'onglet de débogage de TIO)
- Ne pas Déjà. Utilisation. Java. 1. C'est plus bavard que Java en général.
Cela peut briser la chaîne.
- L'écart (7 jours et 48 minutes) n'est rien de plus que l'écart créé par cette réponse , qui est de 7 jours et 1 heure 25 minutes plus tard que la précédente .
Nouveau record sur un grand nombre! Parce que j'utilise (à tort?) Des espaces au lieu de tabulations, le nombre de tours est plus grand que nécessaire. Sur ma machine, c'est 9550 octets. (au moment de la rédaction de cette révision)
- Séquence suivante .
- Le code, dans sa forme actuelle, n’imprime que les 20 premiers termes de la séquence. Cependant , il est facile de changer de sorte qu'il imprime 1000 premiers éléments (par le changement le
20
dans for (int i = 0; i < 20; ++i)
à 1000
)
Yay! Cela peut calculer plus de termes que ceux énumérés sur la page OEIS! (pour la première fois, pour un défi, j’ai besoin d’utiliser Java) à moins que OEIS ait plus de termes quelque part ...
Explication rapide
Explication de la description de la séquence.
La séquence demande le nombre de polyénoïdes non plans libres avec groupe de symétrie C 2v , où:
- polyénoïde: (modèle mathématique des hydrocarbures polyéniques), les arbres (ou dans le cas dégénéré, sommet simple) peuvent être inclus dans un réseau hexagonal.
Par exemple, considérons les arbres
O O O O (3)
| \ / \
| \ / \
O --- O --- O O --- O O --- O
| \
| (2) \
(1) O O
Le premier ne peut pas être intégré dans le réseau hexagonal, alors que le second le peut. Cette intégration particulière est considérée comme différente du troisième arbre.
- polyénoïde non planaire: incrustation d'arbres telle qu'il existe deux sommets qui se chevauchent.
(2)
et l' (3)
arbre ci-dessus sont planes. Celui-ci, cependant, est non plan:
O---O O
/ \
/ \
O O
\ /
\ /
O --- O
(il y a 7 sommets et 6 arêtes)
- polyénoïde libre: Les variantes d'un polyénoïde, obtenues par rotation et réflexion, comptent pour un.
- Groupe C 2v : Les polyénoïdes ne sont comptés que s'ils ont 2 plans de réflexion perpendiculaires, pas plus.
Par exemple, le seul polyénoïde à 2 sommets
O --- O
a 3 plans de réflexion: l’horizontal -
, le vertical |
et l’autre parallèle à l’écran de l’ordinateur ■
. C'est trop.
D'autre part, celui-ci
O --- O
\
\
O
a 2 plans de réflexion: /
et ■
.
Explication de la méthode
Et maintenant, l'approche sur la façon de compter le nombre.
Premièrement, je prends la formule a(n) = A000063(n + 2) - A000936(n)
(listée sur la page OEIS) pour acquise. Je n'ai pas lu l'explication dans le journal.
[TODO réparer cette partie]
Bien sûr, compter en plan est plus facile que de compter en plan. C'est ce que le papier fait aussi.
Les polyénoïdes géométriquement planaires (sans sommets superposés) sont énumérés par programmation informatique. Ainsi, le nombre de polyénoïdes géométriquement non plans devient accessible.
Alors ... le programme compte le nombre de polyénoïde plan et le soustrait du total.
Parce que l’arbre est quand même planaire, il a évidemment le ■
plan de réflexion. La condition se résume donc à "compter le nombre d’arbres avec un axe de réflexion dans sa représentation 2D".
La manière naïve serait de générer tous les arbres avec des n
noeuds et de vérifier la symétrie correcte. Cependant, comme nous voulons seulement trouver le nombre d’arbres avec un axe de réflexion, nous pouvons simplement générer tous les demi-arbres possibles sur une moitié, les refléter sur l’axe, puis vérifier la symétrie correcte. De plus, comme les polyénoïdes générés sont des arbres (plans), il doit toucher l’axe de réflexion exactement une fois.
La fonction public static Graph[] expand(Graph[] graphs, Point.Predicate fn)
prend un tableau de graphes, chacun ayant des n
nœuds, et génère un tableau de graphes, chacun ayant des n+1
nœuds non égaux les uns des autres (en translation) - de sorte que le nœud ajouté doit satisfaire le prédicat fn
.
Considérez 2 axes de réflexion possibles: un qui passe par un sommet et qui coïncide avec des arêtes ( x = 0
) et un qui est la bissectrice perpendiculaire d’une arête ( 2x = y
). Nous ne pouvons en prendre qu'un seul car les graphes générés sont de toute façon isomorphes.
Ainsi, pour le premier axe x = 0
, nous partons du graphe de base constitué d'un seul nœud (1, 0)
(dans le cas où il n
est impair) ou de deux nœuds avec un bord entre (1, 0) - (2, 0)
(dans le cas où il n
est pair), puis nous développons les nœuds de cette manière y > 0
. Cela se fait par la section "Type de réflexion 1" du programme, puis pour chaque graphe généré, se reflète (se reflète) sur l'axe X x = 0
( g.reflectSelfX()
), puis vérifie si sa symétrie est correcte.
Cependant, notez que si n
est divisible par 2, nous avons ainsi compté chaque graphe deux fois, car nous générons également son image miroir par l’axe 2x = y + 3
.
(notez les 2 orange)
Similaire pour l'axe 2x = y
, si (et seulement si) n
est pair, nous partons du point (1, 1)
, générons des graphes tels que 2*x > y
, et reflétons chacun d'eux sur l' 2x = y
axe ( g.reflectSelfType2()
), nous connectons (1, 0)
avec (1, 1)
, et vérifions s'ils ont la symétrie correcte. N'oubliez pas de diviser par 2, aussi.