1. 程式人生 > 其它 >[噼昂!]NOI第x場題解彙總

[噼昂!]NOI第x場題解彙總

\[\color{red}{\text{校長者,真神人也,左馬桶,右永神,會執利筆破邪炁,何人當之?}} \\ \begin{array}{|} \hline \color{pink}{\text{The principal is really a god}} \\ \color{pink}{\text{with a closestool on the left and Yongshen on the right}} \\ \color{pink}{\text{holding a sharp pen to pierce the truth}} \\ \color{pink}{\text{Who can resist him? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{green}{\text{校長は本當に神であり、左側にトイレ、右側にヨンシェンがあり}} \\ \color{green}{\text{鋭いペンを持って真実を突き刺している。誰が彼に抵抗できるだろうか? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{lightblue}{\text{Le principal est vraiment un dieu}} \\ \color{lightblue}{\text{avec des toilettes à gauche et Yongshen à droite}} \\ \color{lightblue}{\text{tenant un stylo pointu pour percer la vérité}} \\ \color{lightblue}{\text{Qui peut lui résister ? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{purple}{\text{Der Direktor ist wirklich ein Gott}} \\ \color{purple}{\text{mit einer Toilette links und Yongshen rechts}} \\ \color{purple}{\text{der einen spitzen Stift hält}} \\ \color{purple}{\text{um die Wahrheit zu durchdringen.}} \\ \color{purple}{\text{Wer kann ihm widerstehen? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{cyan}{\text{Principalis deus est, Yongshen a dextris cum latrina}} \\ \color{cyan}{\text{acuto stylo ad perforandum veritatem: quis resistet ei? }} \\ \hline \end{array} \\ \color{red}{\text{對曰:“無人,狗欲當之,還請賜教!”}} \\ \newcommand\bra[1]{\left({#1}\right)} \newcommand\Bra[1]{\left\{{#1}\right\}} \newcommand\dx[0]{\text{dx}} \newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}} \newcommand\down[2]{{#1}^{\underline{#2}}} \newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor} \newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil} \newcommand\lcm[0]{\operatorname{lcm}} \newcommand\set[1]{\left\{{#1}\right\}} \newcommand\ceil[1]{\left\lceil{#1}\right\rceil} \newcommand\floor[1]{\left\lfloor{#1}\right\rfloor} \]

  這個考試 \(T1,T2\) 太容易假了......這倆題前前後後折騰 \(3h\),搞得後面的題完全沒有時間去看。題目就不寫了。

Problem A. 寶藏 / \(\mathcal{Treasure}\)

  不妨先將寶藏按照 \((w_i,t_i)\) 從小到大排序。不難想到一個 \(\mathcal O(n\log n\log t)\) 的演算法 —— 對於每個寶藏,我們算出 \(f_i=2x+1\) 表示能夠從小於等於它的以及大於等於它的兩個部分中最多選出 \(x\) 個,使得 \(2x+1\) 個寶藏的時間花費小於等於 \(T\). 使用 主席樹/平衡樹 即可解決,具體來說,我們只需要二分 \(x\)

,用資料結構快速檢查 \(T\) 是否符合。

  但是這樣好像有點卡.......實際看上去好像還沒有卡過的,於是我們需要更優秀的做法。我們不妨列舉最終開採的礦石數量,顯然,開採得越多,答案可能越小,於是我們只需要維護一個指標 \(now\),表示當前的答案能夠選擇的最大價值礦物為 \(now\),當我們列舉到某個 \(2x+1\),我們需要檢查,若最終答案為 \(now\),並且選擇了總共 \(2x+1\) 個貨物是否合法,實際上很簡單,兩顆主席樹維護字首、字尾,然後分別在兩個樹上檢查在 \(now\) 前,\(now\) 後各選 \(x\) 個時間最小的,再加上它自己的時間,最終是否會超過 \(T\)

. 該演算法的複雜度為 \(\mathcal O(n\log t)\),事實上我們利用答案單調性省去了二分的時間。

Problem B. 尋找道路 / \(\mathcal {Path}\)

  真的智障了.......想了半天發現好多地方都假了。

  不妨先將與 \(1\)\(0\) 邊的點全部縮起來,處理掉了字首 \(0\) 的情況。對於剩下的點,肯定是走最短路到它,於是我們可以把最短路 \(\rm DAG\) 建出來,然後在上面 \(dfs\),優先走 \(0\) 邊,然後再走 \(1\) 邊。

Problem C. 豬國殺 / \(\mathcal {Legend}\)

  計數題永遠的噩夢......事實上期望只是一個幌子,它的本質還是計數問題。我們只需要把每個序列能夠選擇的牌數統計出來,然後乘上 \((A^n)^{-1}\pmod{998244353}\)

即可。於是,現在的關鍵是,我們如何快速地統計 \(A^n\) 種牌堆中每種牌堆能夠獲得的卡牌數。

  貪心地,由於要求每次取最多的卡牌,我們面對一個序列時會先將其排序,然後儘可能地取最小的,直到總和超過 \(m\) 或者是卡牌取完了。以下反映了這一過程:

\[得到序列\overset{排序}{\longrightarrow}貪心選擇 \]

  暴力的思路一般就是由左至右。但大可不必這樣,我們可以從小到大列舉 \(1,2,...,A\) 每種卡牌,即我們先處理了排序這一過程,同時處理貪心選擇這一過程,在 \(\rm DP\) 過程中使用組合數學還原序列的個數。因此,我們就有了一個 \(\rm DP\) 思路:定義 \(f(x,i,w)\) 表示當前我們考慮到的卡牌大小為 \(x\),當前序列中已經有了 \(i\) 個位置被填上了數,此時已經選擇的數的總和為 \(w\) 時,所有的方案獲得的卡牌數之和,轉移可以列舉當前的 \(x\) 有多少個,由於我們是從小到大選數,因此在剩餘空間 \(m-w\) 夠的情況下,一定是儘可能選擇多的,即若我們列舉 \(i,w\),以及 \(x\) 的牌數 \(k\),那麼我們獲得\(x\) 卡牌數量實際是 \(\min\left(k,\left\lfloor\frac{m-w}{x}\right\rfloor\right)\). 因此轉移 \(f(i,j)\to f(i+k,j+x\times \min\left(k,\left\lfloor\frac{m-w}{x}\right\rfloor\right))\),但是缺少了某些部分 —— 缺少了當前的方案對目標狀態的貢獻,即當前滿足 “有了 \(i\) 個位置被填上了數,此時已經選擇的數的總和為 \(w\) 時” 的序列對目標狀態的貢獻,每種這樣的序列都會多獲得 \(\min\left(k,\left\lfloor\frac{m-w}{x}\right\rfloor\right)\) 張卡牌。因此我們還需要維護序列方案數,設 \(g(x,i,w)\) 表示當前考慮到卡牌大小為 \(x\),當前序列中已經有了 \(i\) 個位置被填上了數,此時已經選擇的數的總和為 \(w\) 時,所有的合法序列數,這樣我們就可以完善轉移了:

\[(f(i,w)+g(i,w)\times y)\times {n-i\choose k}\to f\left(i+k,w+x\times\min\left(k,\left\lfloor\frac{m-w}{x}\right\rfloor\right)\right) \\ g(i,w)\times {n-i\choose k}\to g\left(i+k,w+x\times\min\left(k,\left\lfloor\frac{m-w}{x}\right\rfloor\right)\right) \]

  這個 \(\rm DP\) 複雜度為 \(\mathcal O((mn)^2)\),能夠獲得 \(80pts\) 的高分。不過,其問題在於,需要內外同時列舉值域,而 \(\mathcal O(m^2)\) 是我們所無法承受的。

  回到原來的流程圖,做 \(\rm DP\) 的過程本質是先排序、再選擇、在過程中還原序列。在 \(\rm DP\) 中,由於我們外層列舉排序,故狀態需要記錄剩下倆東西,因此狀態複雜度 \(\mathcal O(nm)\),而外層又有 \(\mathcal O(nm)\) 的列舉,所以炸了。我們不妨從貪心選擇上入手,考察某一張卡牌最後是否會被選,如果會被選,那麼它提供的貢獻就是所有包含它的選擇方案數,而我們希望該方案數能夠被很好地計算出來。我們不妨列舉某張卡牌的權值為 \(x\),它是所有 \(val=x\) 的牌的第 \(k\) 張,那麼,它能夠被選擇,當且僅當前面所有被選的卡牌的總和 \(sum\le m-kx\),於是我們有了一個思路:

\[ans=\sum_{x=1}^A\sum_{t=1}^n\sum_{k=1}^t\sum_{i=0}^{n-t}h(i,x-1,m-kx){n\choose i}{n-i\choose t}(A-j)^{n-i-t} \]

  我們列舉考察的卡牌大小為 \(x\),總共選了 \(t\)\(val=x\) 的牌,當前的牌是所有 \(val=x\) 的第 \(k\) 張時,它能夠被選入的方案,此時我們還需要列舉所有小於 \(x\) 的牌有 \(i\) 張,並且,這 \(i\) 張總和不超過 \(m-kx\) 的方案數,\(h(i,x-1,m-kx)\) 實際上表達的意思就是長度為 \(i\) 的序列,最大值不超過 \(x-1\),總和不超過 \(m-kx\) 的序列方案數。

  接下來我們需要搞定 \(h(i,x-1,m-kx)\),事實上,我們可以通過容斥大於 \(x-1\) 的卡牌獲得:

\[h(i,x-1,m-kx)=\sum_{p=0}^i(-1)^p{i\choose p}{m-kx-p(x-1)\choose i} \]

  後面是隔板法,但是為什麼下面是 \(i\) 而非 \(i-1\) ?因為我們的總和並不一定必須為 \(m-kx\),還可以小一些,多一個板可以把總和搞小一點。於是,我們把這個帶入原式:

\[\begin{aligned}{} (*)&=\sum_{x=1}^A\sum_{t=1}^n\sum_{k=1}^t\sum_{i=0}^{n-t}\sum_{p=0}^i(-1)^p{i\choose p}{m-kx-p(x-1)\choose i}{n\choose i}{n-i\choose t}(A-j)^{n-i-t} \\ \end{aligned} \]

  我們想要降低這個式子的複雜度,注意到有一個組合數 \({m-kx-p(x-1)\choose i}\),若該項為 \(0\),則沒有任何貢獻,而該項為 \(0\) 事實上只需要 \(m\le kx\),它實際上就是一個調和級數,於是,我們希望能夠將 \(x,k,i\) 的列舉放到一起,這樣可以降低複雜度,不用完全列舉而是降低為 \(\mathcal O(\ln m)\).

\[\begin{aligned}{} (*)=\sum_{i=0}^n\sum_{x=1}^A\sum_{k=1}^{n-i}\bra{\sum_{p=0}^i(-1)^p{i\choose p}{m-kx-p(x-1)\choose i}}\bra{\sum_{t=k}^{n-i}{n-i\choose t}(A-j)^{n-i-t}} \end{aligned} \]

  於是我們就可以 \(\mathcal O(n^2m\ln m)\) 計算了。

#include <bits/stdc++.h>
using namespace std;

#define USING_FREAD

namespace Elaina {

#define rep(i, l, r) for (int i = l, i##_end_ = r; i <= i##_end_; ++i)
#define drep(i, l, r) for (int i = l, i##_end_ = r; i >= i##_end_; --i)
#define fi first
#define se second
#define Endl putchar('\n')

    typedef long long ll;
    typedef pair<int, int> pii;

    template<class T> inline T fab(T x) { return x < 0? -x: x; }
    template<class T> inline void getmax(T& x, const T& rhs) { x = max(x, rhs); }
    template<class T> inline void getmin(T& x, const T& rhs) { x = min(x, rhs); }

#ifdef USING_FREAD
# define CHARRECEI qkgetc()
    inline char qkgetc() {
# define BUFFERSIZE 1 << 18
        static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
        return (p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2))? EOF : *p1++;
# undef BUFFERSIZE
    }
#else
# define CHARRECEI getchar()
#endif

    template<class T> inline T readret(T x) {
        x = 0; char c; bool f = false;
        while (!isdigit(c = CHARRECEI)) if (c == '-') f = true;
        for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
        return f? -x: x;
    }
    template<class T> inline void readin(T& x) {
        x = 0; char c; bool f = false;
        while (!isdigit(c = CHARRECEI)) if (c == '-') f = true;
        for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
        if (f) x = -x;
    }
    template<class T, class... Args> inline void readin(T& x, Args&... args) {
        readin(x), readin(args...);
    }
    template<class T> inline void writln(T x, char c = '\n') {
        static int writ_stk[55], writ_ed;
        if (x < 0) putchar('-'), x = -x;
        do writ_stk[++writ_ed] = x % 10, x /= 10; while (x);
        while (writ_ed) putchar(writ_stk[writ_ed--] ^ 48);
        putchar(c);
    }

} using namespace Elaina;

const int mod = 998244353;
const int maxn = 100;
const int maxm = 1000;

inline int qkpow(int a, int n) {
	int ret = 1;
	for (; n; n >>= 1, a = 1ll * a * a % mod)
		if(n & 1) ret = 1ll * ret * a % mod;
	return ret;
}
inline void Add(int& x, const int& rhs) {
	(x += rhs) >= mod? x -= mod: 0;
}

int fac[maxm + 5], ifac[maxm + 5], inv[maxm + 5];
inline void prelude() {
	inv[0] = inv[1] = ifac[0] = ifac[1] = fac[0] = fac[1] = 1;
	int n = maxm;
	for (int i = 2; i <= n; ++i) {
		inv[i] = 1ll * inv[mod % i] * (mod - mod / i) %mod;
		fac[i] = 1ll * fac[i - 1] * i % mod;
		ifac[i] = 1ll * ifac[i - 1] * inv[i] % mod;
	}
}
inline int C(int n, int m) {
	if(n < 0 || m < 0 || n < m) return 0;
	return 1ll * fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}

int n, m, A;
inline void input() { readin(n, m, A); }

#define sign(i) (((i) & 1)? mod - 1: 1)
inline void solve() {
	int ans = 0;
	for (int i = 0; i <= n; ++i) {
		for (int j = 1; j <= A; ++j) {
			for (int k = 1; k <= n - i; ++k) {
				if(m - j * k < 0) break;
				int g = 0, sum = 0;
				for (int x = 0; x <= i; ++x) {
					Add(g, 1ll * sign(x) * C(i, x) % mod * C(m - j * k - x * (j - 1), i) % mod);
					if(m - j * k - x * (j - 1) < i) break;
				}
				for (int t = k; t <= n - i; ++t)
					Add(sum, 1ll * C(n - i, t) * qkpow(A - j, n - i - t) % mod);
				Add(ans, 1ll * C(n, i) * g % mod * sum % mod);
			}
		}
	}
	writln(1ll * ans * qkpow(inv[A], n) % mod);
}

signed main() {
    freopen("legend.in", "r", stdin);
    freopen("legend.out", "w", stdout);
    input();
    prelude();
    solve();
    return 0;
}

Problem D. 數樹 / \(\mathcal{Count}\)

  本題的關鍵在於:它到底想要求什麼。

  事實上題面說得很清楚,我們想要求的是滿足以下條件的合法對映 \(f\)

  1. \(f\) 是雙射;
  2. \(\exists A\subset V_1,f(A)=T_2\),且一條 \(A\) 中的邊,經過 \(f\) 對映之後也一定對應 \(B\) 的一條邊;
  3. \(f_1(A)=T_2,f_2(A)=T_2'\),其中 \(T_2'\)\(T_2\) 同構,即存在一個對映 \(h\),使得 \(h(T_2')=T_2\),且對於一條邊,經過 \(h\) 對映之後也對應 \(T_2\) 的一條邊;

  第一個限制就是一個點對一個點,第二個限制就是這個連通塊和 \(T_2\) 同構,那麼,第三個限制呢?感覺上好像是對映的去重,而我們想要找到的,是本質不同的對映。

  這不禁讓我們想起 \(\rm Burnside\) 引理,我們將對映 \(h\) 看做是作用在對映 \(f\) 上的置換 \(g\),即 \(x\to f(x)\) 變換之後為 \(x\to g[f(x)]\),並且原來 \(A\to f(A)=T_2\),變換之後 \(A\to g[f(A)]=T_2'\),那麼我們將其看做是相同的變換。現在有很多個不同的置換 \(g_1,g_2,\cdots,g_n\),它們構成了一個置換群 \(G\),而現在我們想求的就是 \(|F/G|\),由 \(\rm Burnside\) 引理,我們知道:

\[|F/G|=\frac{1}{|G|}\sum_{g\in G}|F^g| \]

  其中 \(F^g\) 表示 \(F=\set{f_1,f_2,\cdots,f_k}\) 在經過 \(g\) 置換之後的不動點集合。而左邊的東西就是我們最終想要的。

  那麼,我們現在面對了兩個問題:\(|G|\) 以及 \(\sum_{g\in G}|F^g|\). 先解決第一個問題,現在,如果我們發現 \(f_1,f_2,f_3\) 是本質相同的,那麼我們可以畫出下面的一個圖:

  實際上構成一個完全圖,不過沒有畫完。現在有個問題:在這個等價類中,存在多少種置換?直觀地看,似乎是 \(siz^2\),因為任意兩個之間都存在一種置換,不過,事實上只有 \(siz\) 個,舉個例子,\(f_1\to g_2(f_1)\Leftrightarrow f_1\to g_1(f_1)\to g_7[g_1(f_1)]\),也就是說,\(g_2\) 實際上是 \(g_7(g_1)\),或者理解為兩個置換的複合,從矩陣上或許更好理解,同理,\(g_7\) 可以理解為 \(g_1^{-1}\circ g_2\),如果將置換理解為矩陣,那麼可以說是 \(g_1\) 的逆矩陣作用在 \(g_2\) 上,便得到了 \(g_7\). 事實上,\(g_7\) 一定會是 \(g_1,g_2,\cdots,g_5\) 中的一個,可以通過反證法證明這個問題:若 \(g_7\) 並不屬於 \(g_1,g_2,\cdots,g_5\) 中的任意一個,那麼說明 \(g_7=g_1^{-1}\circ g_2\) 為一個新的置換,那麼它作用在 \(f_1\) 上,本應該會形成一個新的對映,但是事實上它形成的是 \(f_2\),這便說明了問題。事實上,\(g_1,g_2,\cdots g_5\) 為一個群,其中 \(g_5\) 為單位元。顯然,一種置換對應在 \(T_2\) 上為一種自同構的對映,因此,\(|G|\) 即為自同構方案數。

  那麼 \(\sum_{g\in G}|F^g|\) 又是什麼?顯然,只有當 \(f\) 經過它自己到自己的置換之後,它會不變,而其他任意置換都會改變 \(f\),因此,對於每一種置換,只有它自己匹配的方案是為不動點,其他任意情況都不是,所以我們只需要統計它自己的情況即可。

  其實兩個部分本質是相同的,不過第一部分是 \(T_2\) 和自己匹配,而第二部分是 \(T_1\) 的連通子集和 \(T_2\) 匹配,我們只需要同樣的樹 \(\rm DP\) 即可。不過,若 \(T_1,T_2\) 都沒有根,那麼樹 \(\rm DP\) 完全沒有辦法做,顯然需要欽定根,顯然選擇 \(T_2\),因為 \(T_2\le 10\). 對於 \(T_1\),隨便找一個點當根即可,定義樹 \(\rm DP\) 狀態 \(dp(u,u',s)\) 表示 \(u\) 對應 \(T_2\)\(u'\),而 \(u\)兒子對應了 \(u'\)\(T_2\) 上的 \(s\) 的兒子。

  轉移的時候列舉 \(u\) 對應的哪個點 \(u'\),列舉 \(u\) 的兒子 \(v\),列舉 \(v\) 對應的是 \(u'\) 的哪個兒子 \(v'\),狀態複雜度 \(\mathcal O(nm2^m)\),轉移複雜度為 \(\mathcal O(nm^22^m)\),因此總複雜度為 \(\mathcal O(nm^22^m)\).

  程式碼裡面附有樹 \(\rm hash\) 加暴力揹包的 \(\rm hack\) 程式碼。

#include <bits/stdc++.h>
using namespace std;

#define USING_FREAD

namespace Elaina {

#define rep(i, l, r) for (int i = l, i##_end_ = r; i <= i##_end_; ++i)
#define drep(i, l, r) for (int i = l, i##_end_ = r; i >= i##_end_; --i)
#define fi first
#define se second
#define Endl putchar('\n')
#define bitcnt(x) (__builtin_popcount(x))

    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;

    template<class T> inline T fab(T x) { return x < 0? -x: x; }
    template<class T> inline void getmax(T& x, const T& rhs) { x = max(x, rhs); }
    template<class T> inline void getmin(T& x, const T& rhs) { x = min(x, rhs); }

#ifdef USING_FREAD
# define CHARRECEI qkgetc()
    inline char qkgetc() {
# define BUFFERSIZE 1 << 18
        static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
        return (p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2))? EOF : *p1++;
# undef BUFFERSIZE
    }
#else
# define CHARRECEI getchar()
#endif

    template<class T> inline T readret(T x) {
        x = 0; char c; bool f = false;
        while (!isdigit(c = CHARRECEI)) if (c == '-') f = true;
        for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
        return f? -x: x;
    }
    template<class T> inline void readin(T& x) {
        x = 0; char c; bool f = false;
        while (!isdigit(c = CHARRECEI)) if (c == '-') f = true;
        for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
        if (f) x = -x;
    }
    template<class T, class... Args> inline void readin(T& x, Args&... args) {
        readin(x), readin(args...);
    }
    template<class T> inline void writln(T x, char c = '\n') {
        static int writ_stk[55], writ_ed;
        if (x < 0) putchar('-'), x = -x;
        do writ_stk[++writ_ed] = x % 10, x /= 10; while (x);
        while (writ_ed) putchar(writ_stk[writ_ed--] ^ 48);
        putchar(c);
    }

} // namespace Elaina
using namespace Elaina;

const int mod = 998244353;
const int maxn = 3000;
const int maxm = 10;
const int maxs = 1 << maxm;
const int maxa = 1e5;

inline void Add(int& x, const int& rhs) { (x += rhs) >= mod? (x -= mod): 0; }

inline int qkpow(int a, int n) {
    int ret = 1;
    for (; n; n >>= 1, a = 1ll * a * a % mod)
        if(n & 1) ret = 1ll * ret * a %mod;
    return ret;
}

template<const int MAXSIZE> struct Tree {

    vector<int> g[MAXSIZE];
    inline void add_edge(int u, int v) {
        g[u].push_back(v), g[v].push_back(u);
    }

};

int n, m;
Tree<maxn + 5> T1;
Tree<maxm + 5> T2;

inline void input() {
    readin(n);
    int u, v;
    for (int i = 1; i < n; ++i) {
        readin(u, v);
        T1.add_edge(u, v);
    }
    readin(m);
    for (int i = 1; i < m; ++i) {
        readin(u, v), --u, --v;
        T2.add_edge(u, v);
    }
}

template<int UPN> void getStatus(Tree<UPN>& T, int u, int par, int* sta) {
    sta[u] = 0;
    for (int v: T.g[u]) if (v ^ par) {
        sta[u] ^= (1 << v);
        getStatus(T, v, u, sta);
    }
}

int dp[maxn + 5][maxm + 5][maxs + 5];
template<int UPN> void runDp(Tree<UPN>& T, int u, int par, int* sta) {
    int siz = 0;
    for (int v: T.g[u]) if (v ^ par)
        runDp(T, v, u, sta), ++siz;
    for (int _u = 0; _u < m; ++_u) {
        for (int s = sta[_u]; s; --s &= sta[_u]) dp[u][_u][s] = 0;
        dp[u][_u][0] = 0;
        if (bitcnt(sta[_u]) > siz) continue;
        dp[u][_u][0] = 1; /** only correspond a root @p _u */
        for (int v: T.g[u]) if (v ^ par) {
            for (int s = sta[_u]; s; --s &= sta[_u]) {
                for (int _v: T2.g[_u]) if(s >> _v & 1) {
                    Add(dp[u][_u][s], 1ll * dp[u][_u][s ^ (1 << _v)] * dp[v][_v][sta[_v]] % mod);
                }
            }
        }
    }
}

int sta[maxm + 5];
inline void solve() {
    int ans = 0, same = 0;
    for (int rt = 0; rt < m; ++rt) {
        getStatus(T2, rt, -1, sta);
        runDp(T1, 1, 0, sta);
        for (int i = 1; i <= n; ++i)
            Add(ans, dp[i][rt][sta[rt]]);
        runDp(T2, 0, -1, sta);
        Add(same, dp[0][rt][sta[rt]]);
    }
    writln(1ll * ans * qkpow(same, mod - 2) % mod);
}

signed main() {
    freopen("count.in", "r", stdin);
    freopen("count.out", "w", stdout);
    input();
    solve();
    return 0;
}

/*

tree hash hack generator :

int s = 1;
int cnt = 0;
printf("1991\n");
for(int i = 1; i <= 1990; i += 10) {
    printf("%d %d\n", s, i + 1);
    printf("%d %d\n", i + 1, i + 2);
    printf("%d %d\n", i + 1, i + 3);
    printf("%d %d\n", i + 3, i + 4);
    printf("%d %d\n", i + 4, i + 5);
    printf("%d %d\n", i + 1, i + 6);
    printf("%d %d\n", i + 6, i + 7);
    printf("%d %d\n", i + 1, i + 8);
    printf("%d %d\n", i + 8, i + 9);
    printf("%d %d\n", i + 8, i + 10);
    cnt += 10;
}
printf("10\n");
printf(R"(
10
1 2
1 3
3 4
4 5
1 6
6 7
1 8
8 9
8 10
)");

*/