```go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
// Global scanner and writer for fast I/O
var scanner *bufio.Scanner
var writer *bufio.Writer
func init() {
scanner = bufio.NewScanner(os.Stdin)
// Set a large buffer to handle potentially large input
buf := make([]byte, 4096*1024)
scanner.Buffer(buf, 4096*1024)
scanner.Split(bufio.ScanWords)
writer = bufio.NewWriter(os.Stdout)
}
func nextInt() int {
scanner.Scan()
val, _ := strconv.Atoi(scanner.Text())
return val
}
func main() {
defer writer.Flush()
if !scanner.Scan() {
return
}
n, _ := strconv.Atoi(scanner.Text())
type Rule struct {
a, b, c, id int
clean bool
}
rules := make([]Rule, 4*n)
// cleanRules[x] stores indices of rules where server x is the 'a' server (source in forward, target in reverse)
// and x is distinct from b and c.
cleanRules := make([][]int, n+1)
cnt := make([]int, n+1)
for i := 1; i <= n; i++ {
// In the target state (after all rules applied forward), each server has 8 processes.
cnt[i] = 8
}
for i := 0; i < 4*n; i++ {
a := nextInt()
b := nextInt()
c := nextInt()
overlap := 0
if a == b {
overlap++
}
if a == c {
overlap++
}
// A rule is "clean" if the target in reverse (a) is different from sources (b, c).
// If it's not clean, it means a is one of b or c.
// In reverse step (remove b, remove c, add a), if a is one of b or c,
// the net change to a is <= 0 (since we remove before add).
// Thus, non-clean rules never increase the load on a beyond current, so they are always safe
// (assuming current state is valid <= 9).
// Clean rules increase load on a by 1. They are only safe if cnt[a] <= 8.
isClean := (overlap == 0)
rules[i] = Rule{a, b, c, i + 1, isClean}
if isClean {
cleanRules[a] = append(cleanRules[a], i)
}
}
// Queue for rules ready to be applied in reverse
q := make([]int, 0, 4*n)
inQueue := make([]bool, 4*n)
applied := make([]bool, 4*n)
// Initially, all counts are 8.
// Clean rules require cnt[a] <= 8. This is satisfied.
// Non-clean rules are always safe.
// So all rules are initially candidates.
for i := 0; i < 4*n; i++ {
q = append(q, i)
inQueue[i] = true
}
ans := make([]int, 0, 4*n)
head := 0
for head < len(q) {
idx := q[head]
head++
inQueue[idx] = false // Remove mark, though we won't add it back if applied
if applied[idx] {
continue
}
r := rules[idx]
// Check validity at the moment of application
// If it's a clean rule, we must ensure adding a process to 'a' doesn't exceed capacity 9.
// i.e., current cnt[a] must be <= 8.
if r.clean && cnt[r.a] > 8 {
// This rule became invalid due to previous operations increasing cnt[a] to 9.
// Skip it. It will be re-added to queue if cnt[a] drops back to 8 later.
continue
}
// Apply rule (Reverse logic)
// Forward: a -> (b, c)
// Reverse: (b, c) -> a (Remove from b and c, Add to a)
applied[idx] = true
ans = append(ans, r.id)
// Update state
// Note: The order of updates matters for intermediate capacity,
// but since we checked validity, we know the "add" part is safe.
// The "remove" parts are always safe (load decreases).
// We just need to trigger updates for rules associated with servers whose load drops.
// Remove from c
cnt[r.c]--
if cnt[r.c] == 8 {
// If load dropped from 9 to 8, clean rules targeting c become valid.
for _, crIdx := range cleanRules[r.c] {
if !applied[crIdx] && !inQueue[crIdx] {
q = append(q, crIdx)
inQueue[crIdx] = true
}
}
}
// Remove from b
cnt[r.b]--
if cnt[r.b] == 8 {
for _, crIdx := range cleanRules[r.b] {
if !applied[crIdx] && !inQueue[crIdx] {
q = append(q, crIdx)
inQueue[crIdx] = true
}
}
}
// Add to a
cnt[r.a]++
// If cnt[a] becomes 9, clean rules targeting a become invalid.
// We don't remove them from queue explicitly; the validity check at loop start handles it.
}
if len(ans) != 4*n {
fmt.Fprintln(writer, "NO")
} else {
fmt.Fprintln(writer, "YES")
// ans contains rules in reverse order (last applied ... first applied)
// We need to print them in forward order (first ... last)
for i := len(ans) - 1; i >= 0; i-- {
if i < len(ans)-1 {
fmt.Fprint(writer, " ")
}
fmt.Fprint(writer, ans[i])
}
fmt.Fprintln(writer)
}
}
```