```go
package main
import (
"bufio"
"io"
"os"
"strconv"
)
const MAXA = 200000
const MOD int64 = 998244353
type Edge struct {
to int
num int
den int
}
type Frame struct {
v int
p int
num int
den int
state int
}
type NodeVal struct {
v int
p int
val int64
}
var spf [MAXA + 1]int
var inv [MAXA + 1]int64
var curCnt [MAXA + 1]int
var minCnt [MAXA + 1]int
var seen [MAXA + 1]int
func gcd(a, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}
func modPow(a int64, e int) int64 {
res := int64(1)
for e > 0 {
if e&1 == 1 {
res = res * a % MOD
}
a = a * a % MOD
e >>= 1
}
return res
}
func touch(p, id int, used *[]int) {
if seen[p] != id {
seen[p] = id
curCnt[p] = 0
minCnt[p] = 0
*used = append(*used, p)
}
}
func applyEnter(num, den, id int, used *[]int) {
x := den
for x > 1 {
p := spf[x]
c := 0
for x%p == 0 {
x /= p
c++
}
touch(p, id, used)
curCnt[p] -= c
if curCnt[p] < minCnt[p] {
minCnt[p] = curCnt[p]
}
}
x = num
for x > 1 {
p := spf[x]
c := 0
for x%p == 0 {
x /= p
c++
}
touch(p, id, used)
curCnt[p] += c
}
}
func applyExit(num, den int) {
x := num
for x > 1 {
p := spf[x]
c := 0
for x%p == 0 {
x /= p
c++
}
curCnt[p] -= c
}
x = den
for x > 1 {
p := spf[x]
c := 0
for x%p == 0 {
x /= p
c++
}
curCnt[p] += c
}
}
func initData() {
for i := 2; i <= MAXA; i++ {
if spf[i] == 0 {
for j := i; j <= MAXA; j += i {
if spf[j] == 0 {
spf[j] = i
}
}
}
}
inv[1] = 1
for i := 2; i <= MAXA; i++ {
inv[i] = MOD - (MOD/int64(i))*inv[int(MOD%int64(i))]%MOD
}
}
func main() {
initData()
data, _ := io.ReadAll(os.Stdin)
idx := 0
readInt := func() int {
for idx < len(data) && (data[idx] < '0' || data[idx] > '9') {
idx++
}
val := 0
for idx < len(data) && data[idx] >= '0' && data[idx] <= '9' {
val = val*10 + int(data[idx]-'0')
idx++
}
return val
}
t := readInt()
out := bufio.NewWriterSize(os.Stdout, 1<<20)
defer out.Flush()
for tc := 1; tc <= t; tc++ {
n := readInt()
adj := make([][]Edge, n+1)
for i := 0; i < n-1; i++ {
u := readInt()
v := readInt()
x := readInt()
y := readInt()
g := gcd(x, y)
x /= g
y /= g
adj[u] = append(adj[u], Edge{to: v, num: y, den: x})
adj[v] = append(adj[v], Edge{to: u, num: x, den: y})
}
used := make([]int, 0, 64)
stack := make([]Frame, 0, 2*n)
stack = append(stack, Frame{v: 1, p: 0, num: 1, den: 1, state: 0})
for len(stack) > 0 {
fr := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if fr.state == 1 {
if fr.p != 0 {
applyExit(fr.num, fr.den)
}
continue
}
if fr.p != 0 {
applyEnter(fr.num, fr.den, tc, &used)
}
stack = append(stack, Frame{v: fr.v, p: fr.p, num: fr.num, den: fr.den, state: 1})
for _, e := range adj[fr.v] {
if e.to == fr.p {
continue
}
stack = append(stack, Frame{v: e.to, p: fr.v, num: e.num, den: e.den, state: 0})
}
}
rootVal := int64(1)
for _, p := range used {
if minCnt[p] < 0 {
rootVal = rootVal * modPow(int64(p), -minCnt[p]) % MOD
}
}
sum := int64(0)
stack2 := make([]NodeVal, 0, n)
stack2 = append(stack2, NodeVal{v: 1, p: 0, val: rootVal})
for len(stack2) > 0 {
cur := stack2[len(stack2)-1]
stack2 = stack2[:len(stack2)-1]
sum += cur.val
if sum >= MOD {
sum -= MOD
}
for _, e := range adj[cur.v] {
if e.to == cur.p {
continue
}
nv := cur.val * int64(e.num) % MOD * inv[e.den] % MOD
stack2 = append(stack2, NodeVal{v: e.to, p: cur.v, val: nv})
}
}
out.WriteString(strconv.FormatInt(sum, 10))
out.WriteByte('\n')
}
}
```