@codeforces - 1097G @ウラジスラフとグレート伝説


@説明@

所定のn点T.ツリー 点Xの各非空集合のための辺の数、F(X)を定義する最小通信ブロックは、Xにすべての点を含んでいます
別の正の整数kが与えられ、検索:
\ [\ SUM \ {X-limits_ \ subseteq \ {1、2、\:\ DOTS \ N- :, \}、\、X- \ NEQ \ varnothing}(F(X-) )^ K \]

モールド10 ^ 9 + 7。

入力
最初の行二つの整数nとk(^ 52≤n≤10、1≤k≤200) 、及び点指標の数を表します。
各n個の整数は、BI、aiは次の2行は、ツリー(1≤ai、bi≤n)の側面を記述する。
出力
出力10 ^ 9 + 7 MOD結果を表す整数。

実施例
インプット
4 1
1 2
2 3
2 4
出力
21

入力
4 2
1 2
2 3
2 4
出力
45

入力
5 3
1 2
2 3
3 4
4 5
出力
780

@解決@

思考の通常の方法であれば、私たちすることができます=それぞれのi F(X)は、iのための統計の数。
しかし、私はO(N ^ 2)実際、さらなる最適化なしの方法を考えることができます。

場合、K = 1の番号Xの統計は、このエッジを含む通信ブロックXに対応するように、我々は、それぞれの側のために考えることができます。これを行うのは非常に簡単です。
サイズkの各エッジセットのために、そしてX Xはそれを含むように対応した通信ブロックを作るどのように多く数える:K≠1、kの予告まだ小さい範囲(K <= 200)は、なぜ我々はアナロジーを拡張するために見ていないときはエッジ。
アナロジーは、そう簡単にはできませんように見えます。

我々は、共通のルーチン、すなわちスターリング反転考える:
[!^ K M = \ sum_ {I} = 0 {M} ^ C(M、I)* S(Kは、I)* I \] \

Sは、スターリングの第二の種類の数を表します。これは、インターネットの多くを証明し、それらを繰り返しません。
なお、場合I>のk S(K、I)= 0、 そうそこ\(M ^ K = \ sum_ {i = 0} ^ {M} C(M、I)* S(K、I)* 私!\)
そこで、元の式を書き換える:
\ [\ SUM \ {X-limits_ \ subseteq \ {1、2、\:\ DOTS \ N- :, \}、\、X- \ NEQ \のvarnothing}(F(X-))= ^ K !\ sum_ {i = 0} ^ {K} S(K、I)* iが(* \和\ limits_ {X \ subseteq \ {1、2、\:\ドット\、N \}、\、X \ NEQの\のvarnothing} C_ {F (X)} ^ {I})\]

私たちは、その後、S(K、i)は*私は、私を列挙 ! 直接カウントすることができます。
パイル後ろ臨床的意義は、「ツリーのサイズiのエッジの集合から選択され、そしてエッジのセットを含む通信ブロックXに対応する点の数を設定します。」実際に
私たちは、当初の目標を達成しました。

私たちは、木のDPを通じてその事をカウントすることができます。
ポイントXの各セットのために、私たちは、この統計情報のX X内のすべてのポイントでLCA。
したがって、転送を容易にするために、我々は[I] [j]はIをルートとするサブツリーを表すFを定義し、設定値よりも大きいか、LCAのXに等しい私は、Jプログラムの辺の数が選択されました。

Jの選択された番組統計LCAエッジがIである場合、新しいサブツリーP、iは、qは、前のサブツリーのルート構成された通信ブロックは、その後、pはデルで新しいプログラムに等しい場合、qは、pはミックスに追加代替CCP jはさえ側面と根私の間のエッジ。
転送することができるか話し合います。
fはしかし、Pを指し示すことができるFが選択されていないことに留意され、上記転送と実質的に同様である(すなわち、P-選択空集合に対応します)。我々は議論するために一人で来る必要があります。

時間複雑性O(NK ^ 2)に変質しないようにするために、我々はkに直接列挙することはできませんが、サイズがサブツリーの大きさ分(サイズ、k)を、列挙すべきであることに留意されたいです。そのような複雑さはO(NK)です。
実際には、これは一般的な木のDPの最適化ルーチンです。しかし、一般的に証明された木のDPは許可証を取得することができますLCAの各点のみの統計オフィスで知っておく必要があり、これはいくつかの問題であることが判明しました。

(から以下に再現このブログ記事)。

最初のk一の≥kサブツリー、Oのコスト(K ^ 2)に結合し、そのようなのN / kの最大値にマージされるまで、いくつかの<サブツリーを考えるので、複雑さはO(NK)です。
そして、<≥kとk個の合併、複雑さは、その後、最大で1回、このプロセスのすべての点O(サイズ* k)を経験しているので、複雑さはO(NK)で検討してください。
そのようなサブツリーのN / kの唯一最大NK従って操作の数を超えていないので、複雑さはO(NK)であるので、最後の二つを合わせ≥kサブツリーです。
したがって、最終的な複雑さはO(NK)であり、サブツリーの大きさの合成コストの複雑さを低減し、分をとることができるキーです。

@acceptedコード@

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 100000;
const int MAXK = 200;
const int MOD = int(1E9) + 7;
inline int add(int a, int b) {return (a + b)%MOD;}
inline int mul(int a, int b) {return 1LL*a*b%MOD;}
struct edge{
    int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = &edges[0];
void addedge(int u, int v) {
    edge *p = (++ecnt);
    p->to = v, p->nxt = adj[u], adj[u] = p;
    p = (++ecnt);
    p->to = u, p->nxt = adj[v], adj[v] = p;
}
int n, k;
int res[MAXK + 5], fct[MAXK + 5], S[MAXK + 5][MAXK + 5];
int func() {
    int ans = 0;
    for(int i=0;i<=k;i++)
        ans = add(ans, mul(mul(S[k][i], fct[i]), res[i]));
    return ans;
}
void init() {
    S[0][0] = 1;
    for(int i=1;i<=k;i++) {
        S[i][0] = 0;
        for(int j=1;j<=i;j++)
            S[i][j] = add(S[i-1][j-1], mul(S[i-1][j], j));
    }
    fct[0] = 1;
    for(int i=1;i<=k;i++)
        fct[i] = mul(fct[i-1], i);
}
int f[MAXK + 5][MAXN + 5], siz[MAXN + 5];
void dfs(int x, int fa) {
    f[0][x] = 1, res[0] = add(res[0], 1), siz[x] = 1;
    for(edge *p=adj[x];p;p=p->nxt) {
        if( p->to == fa ) continue;
        dfs(p->to, x);
        int t1 = min(siz[p->to] - 1, k), t2 = min(siz[x] - 1, k), t3 = min(siz[p->to] + siz[x] - 1, k);
        for(int i=t2;i>=0;i--)
            for(int j=min(t3-i, t1);j>=0;j--) {
                int del = mul(f[i][x], f[j][p->to]);
                res[i+j] = add(res[i+j], del), f[i+j][x] = add(f[i+j][x], del);
                if( i+j+1 <= t3 )
                    res[i+j+1] = add(res[i+j+1], del), f[i+j+1][x] = add(f[i+j+1][x], del);
            }
        for(int i=0;i<=t1;i++) {
            f[i][x] = add(f[i][x], f[i][p->to]);
            if( i + 1 <= t3 )
                f[i + 1][x] = add(f[i + 1][x], f[i][p->to]);
        }
        siz[x] += siz[p->to];
    }
}
int main() {
    scanf("%d%d", &n, &k), init();
    for(int i=1;i<n;i++) {
        int a, b; scanf("%d%d", &a, &b);
        addedge(a, b);
    }
    dfs(1, 0);
    printf("%d\n", func());
}

@詳細@

だから、木のDPは、多くの場合、コードタイプの詳細の多くを簡単に聞こえます。
それのいくつかを理解しやすいコードを見てください。

おすすめ

転載: www.cnblogs.com/Tiw-Air-OAO/p/11621730.html