// 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);
}
}
Çevrimiçi deneyin!
Kenar notu:
- Java 5 ile yerel olarak test edilmiştir (uyarının yazdırılmaması için) - bkz. TIO debug sekmesi)
- Yapma. Hiç. Kullan. Java. 1. Genel olarak Java'dan daha ayrıntılıdır.
Bu, zinciri kırabilir.
- Boşluk (7 gün ve 48 dakika) ile meydana getirilen boşluğun daha fazla Bu yanıt 7 gün ve 1 saat 25 dakika sonra daha, önceki .
Büyük bytecount'ta yeni kayıt! Ben (yanlışlıkla?) Sekmeler yerine boşluk kullandığımdan, bayt sayısı gerekenden daha büyük. Benim makinede öyle 9550 bayt. (bu düzeltmeyi yazarken)
- Sıradaki sıra .
- Kod, geçerli haliyle, dizinin yalnızca ilk 20 terimini yazdırır. Ancak, ilk 1000 öğeyi basacak şekilde değiştirmek kolaydır (
20
girişi for (int i = 0; i < 20; ++i)
değiştirilerek 1000
).
Yuppi! Bu, OEIS sayfasında listelenenden daha fazla terim hesaplayabilir! (ilk kez, bir meydan okuma için Java kullanmam gerekiyor) OEIS'in bir yerde daha fazla şartları olmadığı sürece ...
Hızlı açıklama
Dizi tanımının açıklaması.
Dizisi simetri C grubu ile serbest düzlemsel polyenoid sayısı sormak 2v :
- polenoid: (matematiksel polen hidrokarbon modeli) ağaçları (veya dejenere durumunda, tek köşe) ile altıgen kafes içine gömülebilir.
Örneğin, ağaçları düşünün
O O O O (3)
| \ / \
| \ / \
O --- O --- O O --- O O --- O
| \
| (2) \
(1) O O
Birincisi altıgen kafes içine gömülemezken ikincisi olabilir. Bu belirli gömme üçüncü ağaçtan farklı olarak kabul edilir.
- düzlemsel olmayan polenoid: iki örtüşen tepe noktası olacak şekilde ağaçların gömülmesi.
(2)
ve (3)
yukarıdaki ağaç düzlemseldir. Bununla birlikte, bu düzlemsel değildir:
O---O O
/ \
/ \
O O
\ /
\ /
O --- O
(7 köşe ve 6 kenar vardır)
- serbest polenoid: Rotasyon ve yansıma ile elde edilebilen bir polenoid varyantları, bir olarak sayılır.
- Cı- 2v grubu: bunlar 2 dik yansıma uçakları ve daha fazla varsa polyenoid sadece sayılır.
Örneğin, 2 köşeli tek polenoid
O --- O
3 yansıma düzlemine sahiptir: Yatay -
olan, dikey |
olan ve bilgisayar ekranına paralel olan ■
. Bu çok fazla.
Öte yandan, bu bir
O --- O
\
\
O
2 yansıma düzlemine sahiptir: /
ve ■
.
Yöntemin açıklaması
Ve şimdi, sayının gerçekte nasıl sayılacağı yaklaşımı.
İlk önce formülü alıyorum a(n) = A000063(n + 2) - A000936(n)
(OEIS sayfasında listelenmiştir) . Makalede açıklamayı okumadım.
[TODO bu kısmı düzelt]
Tabii ki, düzlemsel sayımı düzlemsel sayımı yapmaktan daha kolaydır. Kağıdın da yaptığı bu.
Geometrik olarak düzlemsel polenoidler (örtüşen köşeler olmadan) bilgisayar programlaması ile numaralandırılmıştır. Böylece geometrik olarak düzlemsel olmayan polenoidlerin sayıları erişilebilir hale gelir.
Yani ... program düzlemsel polenoid sayısını sayar ve toplamdan çıkarır.
Ağaç zaten düzlemsel olduğundan, açıkça ■
yansıma düzlemine sahip. Böylece durum “2B gösteriminde yansıma eksenine sahip ağaç sayısını saymak” şeklinde azalır.
Saf yol, n
düğümlü tüm ağaçları oluşturur ve doğru simetriyi kontrol eder. Bununla birlikte, yalnızca yansıma eksenine sahip ağaç sayısını bulmak istediğimiz için, mümkün olan tüm yarı ağacı yalnızca bir yarıda oluşturabilir, bunları eksen boyunca aynalayabilir ve sonra doğru simetriyi kontrol edebiliriz. Dahası, üretilen polenoidler (düzlemsel) ağaçlar olduğu için, yansıma eksenine tam olarak bir kez dokunması gerekir.
İşlev public static Graph[] expand(Graph[] graphs, Point.Predicate fn)
bir grafik dizisi alır, her biri n
düğümlere sahiptir ve bir grafik dizisi çıkarır, her biri birbirine n+1
eşit olmayan düğümlere sahiptir (çeviri altında) - eklenen düğümün yüklemeyi yerine getirmesi gerekir fn
.
2 muhtemel yansıma eksenini düşünün: Bir köşeden geçen ve kenarlarla ( x = 0
) çakışan , ve bir kenarın ( 2x = y
) dik kesicisi olan . Bunlardan sadece birini alabiliriz, çünkü oluşturulan grafikler zaten izomorfiktir.
Böylece, ilk eksen için x = 0
, temel grafikten başlıyoruz, tek bir düğümden (1, 0)
( tek durumda n
) veya arasında bir kenar bulunan iki düğümden (1, 0) - (2, 0)
( n
eşit durumda ise) oluşur ve daha sonra düğümleri genişletiriz y > 0
. Bu, programın "Yansıma türü 1" bölümü tarafından yapılır ve sonra oluşturulan her grafik için, kendisini X ekseni x = 0
( g.reflectSelfX()
) üzerinden yansıtır (yansıtır ) ve sonra doğru simetriye sahip olup olmadığını kontrol eder.
Bununla birlikte, eğer n
2'ye bölünebilirse, bu şekilde her grafiği iki kez saydık, çünkü ayna görüntüsünü eksen tarafından da oluşturduk 2x = y + 3
.
(2 turuncu olanı not edin)
Eksen için benzer 2x = y
, (ve sadece) halinde n
bile, biz noktasından başlar (1, 1)
, grafikler bu şekilde elde 2*x > y
ve üzerinde her biri yansıtan 2x = y
eksenine ( g.reflectSelfType2()
), bağlantı (1, 0)
ile birlikte (1, 1)
, ve doğru simetriye sahip olmadığını kontrol edin. Siz de 2'ye bölmeyi unutmayın.