私はしばらくの間この問題で頭蓋骨を叩いてきました、そしてそれは本当に私を苛立たせ始めています。問題は:
A
、B
、C
、およびD
の文字セットがあります。長さがn
であり、各文字が偶数回出現する必要がある場合、これらの文字から文字列を構築する方法をいくつも伝える必要があります。
たとえば、n = 2
の答えは4です。
AA
BB
CC
DD
n = 4
の答えは40です。これらの有効な文字列のいくつかは次のとおりです。
AAAA
AABB
CACA
DAAD
BCCB
私は論理を思い付くのに行き詰まっています。このためのDPソリューションがあると思います。ブルートフォース攻撃は問題外です。ソリューションの数は急速に膨大な数に増えています。
あらゆる種類のアイデアを紙に描いてみましたが、役に立ちませんでした。それらの複雑さが大きすぎるので、私が捨てなければならなかったそれらのアイデアのほとんどすべて。ソリューションはn = 10^4
に対して効率的である必要があります。
私のアイデアの1つは、実際の文字列を追跡するのではなく、各文字が偶数回または奇数回出現したかどうかを追跡することでした。このロジックを適用する方法を思い付くことができませんでした。
誰か助けてもらえますか?
f(n,d)
を、n
個の異なる文字(つまり、あなたの場合は_d=4
_)を使用して、(偶数)長さd
の順列の数を与える関数として設定します。
明らかにf(0,d) = 1
とf(n,1) = 1
は、文字が1つしかない場合、またはスペースがゼロの場合、配置が1つしかないためです。
今、誘導ステップ:
d
文字を使用して有効な文字列を作成するには、_d-1
_文字を使用して短い偶数の長さの文字列を取得し、この新しい文字の偶数倍を追加して長さを調整します。配列の数はchoose(n,n_newdigits)
です。これは、文字列の合計の長さから_n_newdigit
_桁を選択して新しい桁にすることができ、残りは順番に元の文字列で埋められるためです。
Rで単純な再帰を使用してこれを実装するために、次のことを行いました。
_f <- function(n,d)
{
if(n==0) return(1)
if(d==1) return(1)
retval=0
for (i in seq(from=0, to=n, by=2)) retval=retval+f(n-i,d-1)*choose(n,i)
return(retval)
}
f(4,4)
# 40
f(500,4)
# 1.339386e+300 takes about 10 secs
_
興味のある種類の数値については、数値を2次元配列に格納し、dの増加を繰り返す方が効率的だと思いましたが、これは言語の選択によって異なる場合があります。
ミフの答えは間違いなくエレガントです。とにかくほぼ完成したので、それでも提供します。良いことは、n = 500でも同じ結果が得られることです:-)
許可されているさまざまな文字の数をdとすると、d = 4になります。
文字列の長さをnとすると、最終的にはnの偶数の値が表示されます。
文字列内のペアになっていない文字の数をuとします。
N(n、d、u)を長さnの文字列の数とし、d個の異なる文字から構築され、u個のペアになっていない文字を持ちます。 Nを計算してみましょう。
観察すべきかなりのコーナーケースがあります:
u> dまたはu> n => N = 0
u <0 => N = 0
n%2!= u%2 => N = 0。
Nからn + 1にステップするとき、uは1増加するか、1減少する必要があるため、次のように再帰します。
N(n,d,u) = f(N(n-1,d,u-1), N(n-1,d,u+1))
Uを1つ減らす方法はいくつありますか。これは簡単です。ペアになっていないu個の文字の1つをペアにする必要があるため、uだけになります。したがって、fの2番目の部分は(u + 1)* N(n-1、d、u + 1)になります。もちろん、u + 1> n-1またはuの場合、N = 0であることに注意する必要があります。 +1> d。
これを理解すると、fの最初の部分が何であるかを簡単に理解できます。つまり、u-1のペアになっていない文字がある場合、uをいくつの方法で増やすことができますか。さて、ペアになっている(k-(u-1))文字の1つを選択する必要があります。
したがって、すべてのコーナーケースを考慮すると、Nの再帰式は次のようになります。
N(n,d,u) = (d-(u-1))*N(n-1,d,u-1) + (u+1)*N(n-1,d,u+1)
再帰の解決方法を http://en.wikipedia.org/wiki/Concrete_Mathematics で読むつもりはありません。
代わりに、いくつかのJavaコードを記述しました。冗長性のためにJavaとにかく、もう少し不器用です。しかし、再帰を使用しない動機がありました。 、少なくともJavaでは、スタックが500または1000のネストレベルでオーバーフローすると、かなり早く壊れてしまうためです。
N = 500、d = 4、u = 0の結果は次のとおりです。
N(500、4、0)= 133938575898283415118553131132500226320175601463191700930468798546293881390617015311649797351961982265949334114694143353148393160711539255449807219683895854579576904278803546802604812520890471375776580516387245505699580955662718322233732803942258494289
中間結果を記憶しているため、0.2秒で計算されます。 N(40000,4,0)は5秒未満で計算されます。ここにもコードがあります: http://ideone.com/KvB5Jv
import Java.math.BigInteger;
public class EvenPairedString2 {
private final int nChars; // d above, number of different chars to use
private int count = 0;
private Map<Task,BigInteger> memo = new HashMap<>();
public EvenPairedString2(int nChars) {
this.nChars = nChars;
}
/*+******************************************************************/
// encodes for a fixed d the task to compute N(strlen,d,unpaired).
private static class Task {
public final int strlen;
public final int unpaired;
Task(int strlen, int unpaired) {
this.strlen = strlen;
this.unpaired = unpaired;
}
@Override
public int hashCode() {
return strlen*117 ^ unpaired;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Task)) {
return false;
}
Task t2 = (Task)other;
return strlen==t2.strlen && unpaired==t2.unpaired;
}
@Override
public String toString() {
return "("+strlen+","+unpaired+")";
}
}
/*+******************************************************************/
// return corner case or memorized result or null
private BigInteger getMemoed(Task t) {
if (t.strlen==0 || t.unpaired<0 || t.unpaired>t.strlen || t.unpaired>nChars
|| t.strlen%2 != t.unpaired%2) {
return BigInteger.valueOf(0);
}
if (t.strlen==1) {
return BigInteger.valueOf(nChars);
}
return memo.get(t);
}
public int getCount() {
return count;
}
public BigInteger computeNDeep(Task t) {
List<Task> stack = new ArrayList<Task>();
BigInteger result = null;
stack.add(t);
while (stack.size()>0) {
count += 1;
t = stack.remove(stack.size()-1);
result = getMemoed(t);
if (result!=null) {
continue;
}
Task t1 = new Task(t.strlen-1, t.unpaired+1);
BigInteger r1 = getMemoed(t1);
Task t2 = new Task(t.strlen-1, t.unpaired-1);
BigInteger r2 = getMemoed(t2);
if (r1==null) {
stack.add(t);
stack.add(t1);
if (r2==null) {
stack.add(t2);
}
continue;
}
if (r2==null) {
stack.add(t);
stack.add(t2);
continue;
}
result = compute(t1.unpaired, r1, nChars-t2.unpaired, r2);
memo.put(t, result);
}
return result;
}
private BigInteger compute(int u1, BigInteger r1, int u2, BigInteger r2) {
r1 = r1.multiply(BigInteger.valueOf(u1));
r2 = r2.multiply(BigInteger.valueOf(u2));
return r1.add(r2);
}
public static void main(String[] argv) {
int strlen = Integer.parseInt(argv[0]);
int nChars = Integer.parseInt(argv[1]);
EvenPairedString2 eps = new EvenPairedString2(nChars);
BigInteger result = eps.computeNDeep(new Task(strlen, 0));
System.out.printf("%d: N(%d, %d, 0) = %d%n",
eps.getCount(), strlen, nChars,
result);
}
}
私は解決策を考え出そうとしましたが失敗し、 Mathematics.StackExchange で同じ質問をしました。 Rus May のおかげで、CommonLISPの解決策は次のとおりです。
(defun solve (n)
(if (evenp n)
(/ (+ (expt 4 n) (* 4 (expt 2 n))) 8)
0))
これは、n
の奇数値に対して常に0を返します。にとって n = 500
、これが [〜#〜] sbcl [〜#〜] の出力です。
* (time (solve 500))
Evaluation took:
0.000 seconds of real time
0.000000 seconds of total run time (0.000000 user, 0.000000 system)
100.00% CPU
51,100 processor cycles
0 bytes consed
1339385758982834151185531311325002263201756014631917009304687985462938813906170153116497973519619822659493341146941433531483931607115392554498072196838958545795769042788035468026048125208904713757765805163872455056995809556627183222337328039422584942896842901774597806462162357229520744881314972303360