序文
アナログ・マグサイサイSubtaskR3は長い長い失われた20ポイントを開くことができませんでした。
問題の意味の説明
タイトル説明
ルート付きツリーのために(そのノードに設定されている\ N-(\) 、からノード番号\(1 \)をする\(N-を\) )、それは、すべての非ルートノード番号は父より大きい満たす場合我々はそれがYの木であると言います。
また、得られる質問\(K \)整数\(A_1、\ DOTS、\ a_k)を、及び提供は、限りサブツリールート付きツリー内のノードの数が存在するように正確に含まA_1、\ \(ドットは、)\ a_k特定の値は、それが条件を満たしていない、
今与えられた\(N、K、A1、\ DOTS、AK \) 、さらに指定された整数\(L、R&LT \) 、おため\(D = L、Lの+。1、\ DOTS、R&LTの\) 、決定した\(N- \)深ノードである\(D \)適格Yツリーの数です。
データ範囲とヒント
\ [\ {行列} \ {テキスト测试点编号}&を開始| &N \当量&| &K&| &\テキスト{特殊限制} \\ -------& - & - & - & - & - &----- \\ 1,2,3,4,5,6&| &10&| &<N&| &\テキスト{无} \\ -------& - & - & - & - & - &----- \\ 7,8,9,10,11&| &&| &&| &R = 3 \\ -------& - && - && - &----- \\ 12,13,14,15,16&| &100&| &= 0&| &L = N - 2 \\&------- - && - && - &----- \\ 17,18,19&| &&| &&| &&\\ -------& - && - & - & - &----- \\ 20,21,22&| &&| &<N&| &\テキスト{}无&\\ -------& - & - & - && - &\\ 23,24,25&| &500&| &&| &
問題の解決策
サブセクション
暴力についての最初の話は、DFSをすることができます。
まず、書い\(R \の当量3 \)サブセクション。
多くの学生は、法律に一致するようにしようとしたときのために探す必要はありませんが、現実には、法律、
それが唯一のケースがあった場合、ツリーの高さは、2であるとき、ことは明らかである、つまり、私たちはしばしば、「菊マップを。」と言います
木の高さは3倍に増加したときに、明らかにあなたが可能変換を考えることができ、
我々は、ノードの右部分を留保します。
用- \(2 \ L \のGEQの N) 部分分。
第一法則、高い木見つけるプレイテーブル\(N - 1 \) 、プログラム番号を\(\ {N-FRAC \タイムズ(N - 1)} {2} - 2 \) 。
一方の鎖上の他のノードに接続されたノードを除去することが考えられます。
二つの溶液は、ノードを差し引いた\(1 \)ノード計算複数(\ N-)\をノードに再接続することができない\(N-1 \)に
高いツリー\(N - 2 \) 、明らかにチェーン上の2つの点から取り外し、
我々は2つの状況に分けることができます
- 二点一緒に
- 二つの別々のポイント
最後に、マイナスいくつかのダブルカウント部は、断片化され、それが完了です。
コード
WARNING:暴力コードの次のコードを、非常に長く、あなたはスキップすることができます
#pragma GCC optimize(3)
#include <cstdio>
#include <cstring>
#include <vector>
#define MOD 998244353
using namespace std;
int n, k, L, R;
int a[26];
namespace SubtaskBrute{
vector<int> son[26];
int ban[26], sum[26];
int d[26];
long long ans = 0;
int qry;
bool solve(int u){
int u_s = son[u].size(); sum[u] = 1;
for (int i = 0; i < u_s; ++i){
if (!solve(son[u][i])) return 0;
sum[u] += sum[son[u][i]];
}
return (!ban[sum[u]]);
}
void DFS(int u){
if (u == n + 1){ ans += solve(1); return ; }
for (int i = 1; i < u; ++i){
if (d[i] + 1 > qry) continue;
d[u] = d[i] + 1;
son[i].push_back(u);
DFS(u + 1);
son[i].pop_back();
}
}
int res[26];
void index(){
for (int i = 1; i <= k; ++i) ban[a[i]] = 1;
d[1] = 1;
for (qry = L - 1; qry <= R; ++qry){
if (!qry) res[qry] = 0;
else{ ans = 0; DFS(2); res[qry] = ans; }
}
for (int i = L; i <= R; ++i) printf("%lld ", (res[i] - res[i - 1]) % 998244353);
}
}
namespace Subtask1{
int index(int num){
if (num <= 0) return 0;
puts("HJC AK IOI!");
}
}
namespace SubtaskR3{
long long f[505][505];
void index(){
f[1][1] = 1;
for (int i = 2; i <= n; ++i)
for (int j = 1; j < i; ++j)
f[i][j] = (f[i - 1][j - 1] + f[i - 1][j] * j) % MOD;
long long ans = -1;
for (int j = 1; j < n; ++j)
ans = (ans + f[n][j]) % MOD;
if (L <= 1) printf("0 ");
if (L <= 2) printf("1 ");
printf("%lld", ans);
}
}
namespace SubtaskL2{
inline long long getL1(long long n){
return (((n * (n - 1) >> 1) - 2) % 998244353);
}
void index(){
long long ans = 1 - (n << 1);
for (int i = 2; i < n; ++i)
for (int j = i + 1; j <= n; ++j){
ans += i - 1;
if (i == n - 1) --ans;
ans %= MOD;
}
for (int i = 2; i < n; ++i)
for (int j = i + 1; j <= n; ++j){
if (i == n - 1 && j == n)
ans = (ans + (n - 3) * (n - 3)) % MOD;
else{
ans += (i - 1) * (j - 2) % MOD;
ans %= MOD;
if (j == n - 1) ans -= (i - 1);
else if (j == n) ans -= (i - 1);
}
}
printf("%lld", (ans + MOD) % MOD);
if (R >= n - 1) printf(" %lld", getL1(n));
if (R >= n) printf(" 1");
}
}
int main(){
freopen("subtree.in", "r", stdin);
freopen("subtree.out", "w", stdout);
scanf("%d %d", &n, &k);
for (int i = 1; i <= k; ++i) scanf("%d", &a[i]);
scanf("%d %d", &L, &R);
if (n <= 10){ SubtaskBrute::index(); return 0; }
else if (R == 3 && k == 0){ SubtaskR3::index(); return 0; }
else if (L == n - 2 && k == 0){ SubtaskL2::index(); return 0; }
return 0;
}
正解
非常に単純な動的計画。
我々は、状態定義\を(F [i]は[D ] \) の大きさを表し\(Iは\) 、高さ\(D \)の Y-数木の種類。
私たちは道転送を「マージ」への転送の状態を考えると。
二重カウントを避けるために、我々は最も低いノードをマージする小さなノードの特定の時間を定義する(すなわち、最小のルートノード、いずれにしても、ノードが上記少し見かけ接続時間を有していた)
後に、二重カウントを避けるために、我々は合成列挙着信サブ木の大きさ、を挙げることができる式:
\ [F [I] [D] = \ sum_ {J ^ = {I} 1. 1-F} [I - J] [D] \ Fタイムズ[J] [D-1]回\は\
左(\ {行列}私始まる- 2 \\ J - 1 \エンド{行列} \権利を)\] その後、あなたはそれが奇妙な、これはまだ条件を制限することを検討していないことを見つけるかもしれませんか?
実際には、我々は唯一のDP、サブツリーの限られたサイズの制約、DPの値をクリアすることができますする必要があります。
コード
#include <cstdio>
#define MOD 998244353
int C[505][505], f[505][505];
bool ban[505];
int main(){
int n, k; scanf("%d %d", &n, &k);
for (int i = 0; i <= n; ++i){
C[i][0] = 1;
for (int j = 1; j <= i; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
for (int i = 1; i <= k; ++i){
int x; scanf("%d", &x);
ban[x] = 1;
}
if (ban[1]){
int L, R; scanf("%d %d", &L, &R);
for (int i = L; i <= R; ++i)
printf("0%c", (i != R ? ' ' : '\n'));
return 0;
}
f[1][1] = 1;
for (int d = 2; d <= n; ++d){
f[1][d] = 1;
for (int i = 2; i <= n; ++i)
for (int j = 1; j < i; ++j)
f[i][d] = (f[i][d] + 1ll * f[i - j][d] * f[j][d - 1] % MOD * C[i - 2][j - 1]) % MOD;
for (int i = 1; i <= n; ++i)
if (ban[i]) f[i][d] = 0;
}
int L, R; scanf("%d %d", &L, &R);
for (int i = L; i <= R; ++i)
printf("%d%c", (f[n][i] - f[n][i - 1] + MOD) % MOD, (i != R ? ' ' : '\n'));
return 0;
}