```go
package main
import (
"bufio"
"os"
"strconv"
)
func main() {
// Increase buffer size to handle large input efficiently
const maxCapacity = 2048 * 1024
buf := make([]byte, maxCapacity)
scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(buf, maxCapacity)
scanner.Split(bufio.ScanWords)
writer := bufio.NewWriter(os.Stdout)
defer writer.Flush()
// Helper to read the next integer
readInt := func() int {
scanner.Scan()
val, _ := strconv.Atoi(scanner.Text())
return val
}
if !scanner.Scan() {
return
}
t, _ := strconv.Atoi(scanner.Text())
for i := 0; i < t; i++ {
solve(readInt, writer)
}
}
func solve(readInt func() int, writer *bufio.Writer) {
n := readInt()
m := readInt()
// Build adjacency list
adj := make([][]int, n)
hasSelfLoop := make([]bool, n)
for i := 0; i < m; i++ {
u := readInt() - 1
v := readInt() - 1
adj[u] = append(adj[u], v)
if u == v {
hasSelfLoop[u] = true
}
}
// Tarjan's algorithm to find SCCs
ids := make([]int, n)
low := make([]int, n)
onStack := make([]bool, n)
stack := make([]int, 0, n)
for i := 0; i < n; i++ {
ids[i] = -1
}
idCounter := 0
sccCount := 0
sccID := make([]int, n)
var dfs func(int)
dfs = func(at int) {
stack = append(stack, at)
onStack[at] = true
ids[at] = idCounter
low[at] = idCounter
idCounter++
for _, to := range adj[at] {
if ids[to] == -1 {
dfs(to)
if low[to] < low[at] {
low[at] = low[to]
}
} else if onStack[to] {
if ids[to] < low[at] {
low[at] = ids[to]
}
}
}
if ids[at] == low[at] {
for {
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]
onStack[node] = false
sccID[node] = sccCount
if node == at {
break
}
}
sccCount++
}
}
for i := 0; i < n; i++ {
if ids[i] == -1 {
dfs(i)
}
}
// Group vertices by SCC and identify internal loops
nodesInSCC := make([][]int, sccCount)
sccInternalLoop := make([]bool, sccCount)
for u := 0; u < n; u++ {
id := sccID[u]
nodesInSCC[id] = append(nodesInSCC[id], u)
if hasSelfLoop[u] {
sccInternalLoop[id] = true
}
}
for id := 0; id < sccCount; id++ {
if len(nodesInSCC[id]) > 1 {
sccInternalLoop[id] = true
}
}
// DP on SCC DAG
// Tarjan's generates SCCs in reverse topological order (sinks first).
// So, iterating from sccCount-1 down to 0 processes in topological order.
dpReach := make([]bool, sccCount)
dpInf := make([]bool, sccCount)
dpCount := make([]int, sccCount)
// Base case: vertex 1 (index 0)
startID := sccID[0]
dpReach[startID] = true
dpCount[startID] = 1
for i := sccCount - 1; i >= 0; i-- {
if !dpReach[i] {
continue
}
// If this SCC has internal cycle or already marked infinite
if sccInternalLoop[i] {
dpInf[i] = true
}
isInf := dpInf[i]
count := dpCount[i]
// Propagate to neighbors
for _, u := range nodesInSCC[i] {
for _, v := range adj[u] {
target := sccID[v]
if target == i {
continue // Edge within the same SCC
}
dpReach[target] = true
if isInf {
dpInf[target] = true
} else {
if !dpInf[target] {
dpCount[target] += count
if dpCount[target] > 2 {
dpCount[target] = 2
}
}
}
}
}
}
// Output results
for i := 0; i < n; i++ {
id := sccID[i]
if !dpReach[id] {
writer.WriteString("0")
} else if dpInf[id] {
writer.WriteString("-1")
} else {
if dpCount[id] == 1 {
writer.WriteString("1")
} else {
writer.WriteString("2")
}
}
if i == n-1 {
writer.WriteByte('\n')
} else {
writer.WriteByte(' ')
}
}
}
```