You are given a directed graph, consisting of n vertices and m edges. The vertices s and t are marked as source and sink correspondingly. Additionally, there are no edges ending at s and there are no edges beginning in t.The graph was constructed in a following way: initially each edge had capacity ci > 0. A maximum flow with source at s and sink at t was constructed in this flow network. Let's denote fi as the value of flow passing through edge with index i. Next, all capacities ci and flow value fi were erased. Instead, indicators gi were written on edges — if flow value passing through edge i was positive, i.e. 1 if fi > 0 and 0 otherwise.Using the graph and values gi, find out what is the minimum possible number of edges in the initial flow network that could be saturated (the passing flow is equal to capacity, i.e. fi = ci). Also construct the corresponding flow network with maximum flow in it.A flow in directed graph is described by flow values fi on each of the edges so that the following conditions are satisfied:   for each vertex, except source and sink, total incoming flow and total outcoming flow are equal,  for each edge 0 ≤ fi ≤ ci A flow is maximum if the difference between the sum of flow values on edges from the source, and the sum of flow values on edges to the source (there are no such in this problem), is maximum possible.

The first line of input data contains four positive integers n, m, s, t (2 ≤ n ≤ 100, 1 ≤ m ≤ 1000, 1 ≤ s, t ≤ n, s ≠ t) — the number of vertices, the number of edges, index of source vertex and index of sink vertex correspondingly. Each of next m lines of input data contain non-negative integers ui, vi, gi (1 ≤ ui, vi ≤ n, ) — the beginning of edge i, the end of edge i and indicator, which equals to 1 if flow value passing through edge i was positive and 0 if not. It's guaranteed that no edge connects vertex with itself. Also it's guaranteed that there are no more than one edge between each ordered pair of vertices and that there exists at least one network flow that satisfies all the constrains from input data.

In the first line print single non-negative integer k — minimum number of edges, which should be saturated in maximum flow. In each of next m lines print two integers fi, ci (1 ≤ ci ≤ 109, 0 ≤ fi ≤ ci) — the flow value passing through edge i and capacity of edge i.  This data should form a correct maximum flow in flow network. Also there must be exactly k edges with statement fi = ci satisfied. Also statement fi > 0 must be true if and only if gi = 1. If there are several possible answers, print any of them.

NoteThe illustration for second sample case. The saturated edges are marked dark, while edges with gi = 0 are marked with dotted line. The integer on edge is the index of this edge in input list.

/+ dub.sdl:
    name "E"
    dependency "dcomp" version=">=0.6.0"
+/

import std.stdio, std.algorithm, std.range, std.conv;
// import dcomp.foundation, dcomp.scanner;

// import dcomp.graph.maxflow;

int main() {
    auto sc = new Scanner(stdin);
    int n, m, s, t;
    sc.read(n, m, s, t); s--; t--;

    struct E {
        int to, cap, rev;
    }
    void addEdge(E[][] g, int from, int to, int cap) {
        g[from] ~= E(to, cap, g[to].length.to!int);
        g[to] ~= E(from, 0, g[from].length.to!int-1);
    }

    E[][] g = new E[][](n);

    struct E2 {
        int to, id;
    }
    E2[][] gn = new E2[][](n);

    bool[] used = new bool[n];
    int[] pa;
    bool dfs(int p, int q) {
        if (used[p]) return false;
        if (p == q) return true;
        used[p] = true;
        foreach (e; gn[p]) {
            if (dfs(e.to, q)) {
                pa ~= e.id;
                return true;
            }
        }
        return false;
    }
    bool make(int p, int q) {
        used[] = false;
        pa = new int[0];
        return dfs(p, q);
    }

    int[] U = new int[m], V = new int[m], G = new int[m];
    foreach (i; 0..m) {
        int u, v, h; sc.read(u, v, h); u--; v--;
        U[i] = u; V[i] = v; G[i] = h;
        if (h == 0) {
            addEdge(g, u, v, 100000);
        } else {
            addEdge(g, u, v, 1);
            addEdge(g, v, u, 100000);
            gn[u] ~= E2(v, i);
        }
    }
    int[] sm = new int[m];
    foreach (i; 0..m) {
        int u = U[i], v = V[i];
        if (G[i] == 0) continue;
        sm[i]++;
        if (make(v, u)) {
            foreach (d; pa) {
                sm[d]++;
            }
            continue;
        }
        make(s, u);
        foreach (d; pa) {
            sm[d]++;
        }
        make(v, t);
        foreach (d; pa) {
            sm[d]++;
        }
    }
    auto mfI = maxFlow!(int, 0)(g, s, t);
    writeln(mfI.flow);
    foreach (i; 0..m) {
        int u = U[i], v = V[i];
        int di = 0;
        bool f;
        if (G[i] == 0) {
            f = false;
        } else {
            f = !mfI.dual[u] && mfI.dual[v];
        }
        writeln(sm[i], " ", sm[i] + (f ? 0 : 1));
    }
    return 0;
}
/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/graph/maxflow.d */
// module dcomp.graph.maxflow;

// import dcomp.container.deque;

struct maxFlowInfo(C) {
    C flow;
    bool[] dual;
}

maxFlowInfo!(C) maxFlow(C, C EPS, T)(T g, int s, int t) {
    assert(s != t);
    import std.algorithm : map;
    import std.range : array;
    import std.conv : to;
    int n = g.length.to!int;
    int[] level = new int[n];
    int[] iter = new int[n];

    void bfs() {
        level[] = -1; level[s] = 0;
        auto que = Deque!int(s);
        while (!que.empty) {
            int v = que.back; que.removeBack();
            foreach (e; g[v]) {
                if (e.cap <= EPS) continue;
                if (level[e.to] < 0) {
                    level[e.to] = level[v] + 1;
                    que.insertBack(e.to);
                }
            }
        }
    }

    C dfs(int v, int t, C f) {
        import std.algorithm : min;
        if (v == t) return f;
        auto edgeList = g[v][iter[v]..$];
        foreach (ref e; edgeList) {
            if (e.cap <= EPS) continue;
            if (level[v] < level[e.to]) {
                C d = dfs(e.to, t, min(f, e.cap));
                if (d <= EPS) continue;
                e.cap -= d;
                g[e.to][e.rev].cap += d;
                return d;
            }
            iter[v]++;
        }
        return 0;
    }

    C flow = 0;
    while (true) {
        bfs();
        if (level[t] < 0) break;
        iter[] = 0;
        while (true) {
            C f = dfs(s, t, C.max);
            if (!f) break;
            flow += f;
        }
    }

    auto mfInfo = maxFlowInfo!C();
    mfInfo.flow = flow;
    mfInfo.dual = level.map!"a == -1".array;
    return mfInfo;
}

/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/foundation.d */
// module dcomp.foundation;

static if (__VERSION__ <= 2070) {
    template fold(fun...) if (fun.length >= 1) {
        auto fold(R, S...)(R r, S seed) {
            import std.algorithm : reduce;
            static if (S.length < 2) {
                return reduce!fun(seed, r);
            } else {
                import std.typecons : tuple;
                return reduce!fun(tuple(seed), r);
            }
        }
    }

}
version (X86) static if (__VERSION__ < 2071) {
    import core.bitop : bsf, bsr, popcnt;
    int bsf(ulong v) {
        foreach (i; 0..64) {
            if (v & (1UL << i)) return i;
        }
        return -1;
    }
    int bsr(ulong v) {
        foreach_reverse (i; 0..64) {
            if (v & (1UL << i)) return i;
        }
        return -1;
    }
    int popcnt(ulong v) {
        int c = 0;
        foreach (i; 0..64) {
            if (v & (1UL << i)) c++;
        }
        return c;
    }
}
/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/container/deque.d */
// module dcomp.container.deque;

struct Deque(T, bool mayNull = true) {
    import core.exception : RangeError;
    import core.memory : GC;
    import std.range : ElementType, isInputRange;
    import std.traits : isImplicitlyConvertible;

    struct Payload {
        T *d;
        size_t st, length, cap;
        @property bool empty() const { return length == 0; }
        alias opDollar = length;
        ref inout(T) opIndex(size_t i) inout {
            version(assert) if (length <= i) throw new RangeError();
            return d[(st+i >= cap) ? (st+i-cap) : st+i];
        }
        private void expand() {
            import std.algorithm : max;
            assert(length == cap);
            auto nc = max(size_t(4), 2*cap);
            T* nd = cast(T*)GC.malloc(nc * T.sizeof);
            foreach (i; 0..length) {
                nd[i] = this[i];
            }
            d = nd; st = 0; cap = nc;
        }
        void clear() {
            st = length = 0;
        }
        void insertFront(T v) {
            if (length == cap) expand();
            if (st == 0) st += cap;
            st--; length++;
            this[0] = v;
        }
        void insertBack(T v) {
            if (length == cap) expand();
            length++;
            this[length-1] = v;
        }
        void removeFront() {
            assert(!empty, "Deque.removeFront: Deque is empty");
            st++; length--;
            if (st == cap) st = 0;
        }
        void removeBack() {
            assert(!empty, "Deque.removeBack: Deque is empty");
            length--;
        }
    }
    struct RangeT(A) {
        alias T = typeof(*(A.p));
        alias E = typeof(A.p.d[0]);
        T *p;
        size_t a, b;
        @property bool empty() const { return b <= a; }
        @property size_t length() const { return b-a; }
        @property RangeT save() { return RangeT(p, a, b); }
        @property RangeT!(const A) save() const {
            return typeof(return)(p, a, b);
        }
        alias opDollar = length;
        @property ref inout(E) front() inout { return (*p)[a]; }
        @property ref inout(E) back() inout { return (*p)[b-1]; }
        void popFront() {
            version(assert) if (empty) throw new RangeError();
            a++;
        }
        void popBack() {
            version(assert) if (empty) throw new RangeError();
            b--;
        }
        ref inout(E) opIndex(size_t i) inout { return (*p)[i]; }
        RangeT opSlice() { return this.save; }
        RangeT opSlice(size_t i, size_t j) {
            version(assert) if (i > j || a + j > b) throw new RangeError();
            return typeof(return)(p, a+i, a+j);
        }
        RangeT!(const A) opSlice() const { return this.save; }
        RangeT!(const A) opSlice(size_t i, size_t j) const {
            version(assert) if (i > j || a + j > b) throw new RangeError();
            return typeof(return)(p, a+i, a+j);
        }
    }

    alias Range = RangeT!Deque;
    alias ConstRange = RangeT!(const Deque);
    alias ImmutableRange = RangeT!(immutable Deque);

    Payload* p;
    private void I() { if (mayNull && !p) p = new Payload(); }
    private void C() const {
        version(assert) if (mayNull && !p) throw new RangeError();
    }
    static if (!mayNull) {
        @disable this();
    }

    private this(Payload* p) {
        this.p = p;
    }
    this(U)(U[] values...) if (isImplicitlyConvertible!(U, T)) {
        p = new Payload();
        foreach (v; values) {
            insertBack(v);
        }
    }

    this(Range)(Range r)
    if (isInputRange!Range &&
    isImplicitlyConvertible!(ElementType!Range, T) &&
    !is(Range == T[])) {
        p = new Payload();
        foreach (v; r) {
            insertBack(v);
        }
    }
    static Deque make() { return Deque(new Payload()); }
    @property bool havePayload() const { return (!mayNull || p); }

    @property bool empty() const { return (!havePayload || p.empty); }

    @property size_t length() const { return (havePayload ? p.length : 0); }

    alias opDollar = length;
    ref inout(T) opIndex(size_t i) inout {C; return (*p)[i]; }

    ref inout(T) front() inout {C; return (*p)[0]; }

    ref inout(T) back() inout {C; return (*p)[$-1]; }
    void clear() { if (p) p.clear(); }

    void insertFront(T v) {I; p.insertFront(v); }

    void insertBack(T v) {I; p.insertBack(v); }

    alias stableInsertBack = insertBack;

    void removeFront() {C; p.removeFront(); }

    void removeBack() {C; p.removeBack(); }

    Range opSlice() {I; return Range(p, 0, length); }
}

/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/scanner.d */
// module dcomp.scanner;

class Scanner {
    import std.stdio : File;
    import std.conv : to;
    import std.range : front, popFront, array, ElementType;
    import std.array : split;
    import std.traits : isSomeChar, isStaticArray, isArray;
    import std.algorithm : map;
    File f;
    this(File f) {
        this.f = f;
    }
    char[512] lineBuf;
    char[] line;
    private bool succ() {
        import std.range.primitives : empty, front, popFront;
        import std.ascii : isWhite;
        while (true) {
            while (!line.empty && line.front.isWhite) {
                line.popFront;
            }
            if (!line.empty) break;
            if (f.eof) return false;
            line = lineBuf[];
            f.readln(line);
        }
        return true;
    }

    private bool readSingle(T)(ref T x) {
        import std.algorithm : findSplitBefore;
        import std.string : strip;
        import std.conv : parse;
        if (!succ()) return false;
        static if (isArray!T) {
            alias E = ElementType!T;
            static if (isSomeChar!E) {

                auto r = line.findSplitBefore(" ");
                x = r[0].strip.dup;
                line = r[1];
            } else {
                auto buf = line.split.map!(to!E).array;
                static if (isStaticArray!T) {

                    assert(buf.length == T.length);
                }
                x = buf;
                line.length = 0;
            }
        } else {
            x = line.parse!T;
        }
        return true;
    }
    int read(T, Args...)(ref T x, auto ref Args args) {
        if (!readSingle(x)) return 0;
        static if (args.length == 0) {
            return 1;
        } else {
            return 1 + read(args);
        }
    }
}

import std.conv, std.functional, std.range, std.stdio, std.string;
import std.algorithm, std.array, std.bigint, std.bitmanip, std.complex, std.container, std.math, std.mathspecial, std.numeric, std.regex, std.typecons;
import core.bitop;

class EOFException : Throwable { this() { super("EOF"); } }
string[] tokens;
string readToken() { for (; tokens.empty; ) { if (stdin.eof) { throw new EOFException; } tokens = readln.split; } auto token = tokens.front; tokens.popFront; return token; }
int readInt() { return readToken.to!int; }
long readLong() { return readToken.to!long; }
real readReal() { return readToken.to!real; }

bool chmin(T)(ref T t, in T f) { if (t > f) { t = f; return true; } else { return false; } }
bool chmax(T)(ref T t, in T f) { if (t < f) { t = f; return true; } else { return false; } }

int binarySearch(alias pred, T)(in T[] as) { int lo = -1, hi = cast(int)(as.length); for (; lo + 1 < hi; ) { const mid = (lo + hi) >> 1; (unaryFun!pred(as[mid]) ? hi : lo) = mid; } return hi; }
int lowerBound(T)(in T[] as, T val) { return as.binarySearch!(a => (a >= val)); }
int upperBound(T)(in T[] as, T val) { return as.binarySearch!(a => (a > val)); }

class MaxFlow(Capa) {
  enum Capa wEPS = 0;
  enum Capa wINF = 10^^9;
  int n, m;
  int[][] g;
  int[] zu;
  Capa[] capa;
  Capa tof;
  int[] lev, see, que;
  this(int n) {
    this.n = n; m = 0; g = new int[][n]; zu = []; capa = [];
    lev = new int[n]; see = new int[n]; que = new int[n];
  }
  void addEdge(int u, int v, Capa w0, Capa w1 = 0) {
    g[u] ~= m; zu ~= v; capa ~= w0; ++m;
    g[v] ~= m; zu ~= u; capa ~= w1; ++m;
  }
  Capa augment(int src, int ink, Capa flo) {
    if (src == ink) return flo;
    foreach (i; g[src][see[src] .. $]) {
      if (capa[i] > wEPS && lev[src] < lev[zu[i]]) {
        Capa f = augment(zu[i], ink, min(flo, capa[i]));
        if (f > wEPS) { capa[i] -= f; capa[i ^ 1] += f; return f; }
      }
      ++see[src];
    }
    return 0;
  }
  bool dinic(int src, int ink, Capa flo = wINF) {
    for (tof = 0; tof + wEPS < flo; ) {
      int[] q;
      lev[] = -1;
     dinicBFS:
      for (lev[src] = 0, q ~= src; !q.empty; ) {
        int u = q.front; q.popFront;
        foreach (i; g[u]) {
          int v = zu[i];
          if (capa[i] > wEPS && lev[v] == -1) {
            lev[v] = lev[u] + 1; q ~= v;
            if (v == ink) break dinicBFS;
          }
        }
      }
      if (lev[ink] == -1) return false;
      see[] = 0;
      for (; ; ) {
        Capa f = augment(src, ink, flo - tof);
        if (f <= wEPS) break;
        tof += f;
      }
    }
    return true;
  }
}

void main() {
  try {
    for (; ; ) {
      const N = readInt();
      const M = readInt();
      const S = readInt() - 1;
      const T = readInt() - 1;
      auto A = new int[M];
      auto B = new int[M];
      auto G = new int[M];
      foreach (i; 0 .. M) {
        A[i] = readInt() - 1;
        B[i] = readInt() - 1;
        G[i] = readInt();
      }

      auto mf0 = new MaxFlow!int(N);
      foreach (i; 0 .. M) {
        if (G[i] == 1) {
          mf0.addEdge(A[i], B[i], 1);
          mf0.addEdge(B[i], A[i], M + 1);
        } else {
          mf0.addEdge(A[i], B[i], M + 1);
        }
      }
      mf0.dinic(S, T);
      assert(mf0.tof <= M);
      auto d = new bool[][](N, N);
      foreach (u; 0 .. N) {
        d[u][u] = true;
      }
      foreach (i; 0 .. mf0.m) {
        if (mf0.capa[i] > 0) {
          d[mf0.zu[i ^ 1]][mf0.zu[i]] = true;
        }
      }
      foreach (w; 0 .. N) foreach (u; 0 .. N) foreach (v; 0 .. N) {
        if (d[u][w] && d[w][v]) {
          d[u][v] = true;
        }
      }

      auto ex = new int[N];
      foreach (i; 0 .. M) {
        if (G[i] == 1) {
          --ex[A[i]];
          ++ex[B[i]];
        }
      }
      debug {
        writeln("ex = ", ex);
      }
      auto mf1 = new MaxFlow!int(N);
      auto ids1 = new int[M];
      ids1[] = -1;
      foreach (u; 0 .. N) {
        if (u != S && u != T) {
          if (ex[u] < 0) {
            mf1.addEdge(S, u, -ex[u]);
          } else {
            mf1.addEdge(u, T, ex[u]);
          }
        }
      }
      foreach (i; 0 .. M) {
        if (G[i] == 1) {
          ids1[i] = mf1.m;
          mf1.addEdge(A[i], B[i], M);
        }
      }
      mf1.dinic(S, T);

      writeln(mf0.tof);
      foreach (i; 0 .. M) {
        int f, c;
        if (G[i] == 1) {
          f = 1 + mf1.capa[ids1[i] ^ 1];
          c = (d[S][A[i]] && !d[S][B[i]]) ? f : (f + 1);
        } else {
          f = 0;
          c = 1;
        }
        writeln(f, " ", c);
      }
    }
  } catch (EOFException e) {
  }
}

/+ dub.sdl:
    name "E"
    dependency "dcomp" version=">=0.6.0"
+/

import std.stdio, std.algorithm, std.range, std.conv;
// import dcomp.foundation, dcomp.scanner;

// import dcomp.graph.maxflow;

int main() {
    auto sc = new Scanner(stdin);
    int n, m, s, t;
    sc.read(n, m, s, t); s--; t--;

    struct E {
        int to, cap, rev;
    }
    void addEdge(E[][] g, int from, int to, int cap) {
        g[from] ~= E(to, cap, g[to].length.to!int);
        g[to] ~= E(from, 0, g[from].length.to!int-1);
    }

    E[][] g = new E[][](n);

    struct E2 {
        int to, id;
    }
    E2[][] gn = new E2[][](n);

    bool[] used = new bool[n];
    int[] pa;
    bool dfs(int p, int q) {
        if (used[p]) return false;
        if (p == q) return true;
        used[p] = true;
        foreach (e; gn[p]) {
            if (dfs(e.to, q)) {
                pa ~= e.id;
                return true;
            }
        }
        return false;
    }
    bool make(int p, int q) {
        used[] = false;
        pa = new int[0];
        return dfs(p, q);
    }

    int[] U = new int[m], V = new int[m], G = new int[m];
    foreach (i; 0..m) {
        int u, v, h; sc.read(u, v, h); u--; v--;
        U[i] = u; V[i] = v; G[i] = h;
        if (h == 0) {
            addEdge(g, u, v, 100000);
            addEdge(g, v, u, 1);
        } else {
            addEdge(g, u, v, 1);
            addEdge(g, v, u, 100000);
            gn[u] ~= E2(v, i);
        }
    }
    int[] sm = new int[m];
    foreach (i; 0..m) {
        int u = U[i], v = V[i];
        if (G[i] == 0) continue;
        sm[i]++;
        if (make(v, u)) {
            foreach (d; pa) {
                sm[d]++;
            }
            continue;
        }
        make(s, u);
        foreach (d; pa) {
            sm[d]++;
        }
        make(v, t);
        foreach (d; pa) {
            sm[d]++;
        }
    }
    auto mfI = maxFlow!(int, 0)(g, s, t);
    writeln(mfI.flow);
    foreach (i; 0..m) {
        int u = U[i], v = V[i];
        int di = 0;
        bool f;
        if (G[i] == 0) {
            f = !mfI.dual[v] && mfI.dual[u];
        } else {
            f = !mfI.dual[u] && mfI.dual[v];
        }
        writeln(sm[i], " ", sm[i] + (f ? 0 : 1));
    }
    return 0;
}
/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/graph/maxflow.d */
// module dcomp.graph.maxflow;

// import dcomp.container.deque;

struct maxFlowInfo(C) {
    C flow;
    bool[] dual;
}

maxFlowInfo!(C) maxFlow(C, C EPS, T)(T g, int s, int t) {
    assert(s != t);
    import std.algorithm : map;
    import std.range : array;
    import std.conv : to;
    int n = g.length.to!int;
    int[] level = new int[n];
    int[] iter = new int[n];

    void bfs() {
        level[] = -1; level[s] = 0;
        auto que = Deque!int(s);
        while (!que.empty) {
            int v = que.back; que.removeBack();
            foreach (e; g[v]) {
                if (e.cap <= EPS) continue;
                if (level[e.to] < 0) {
                    level[e.to] = level[v] + 1;
                    que.insertBack(e.to);
                }
            }
        }
    }

    C dfs(int v, int t, C f) {
        import std.algorithm : min;
        if (v == t) return f;
        auto edgeList = g[v][iter[v]..$];
        foreach (ref e; edgeList) {
            if (e.cap <= EPS) continue;
            if (level[v] < level[e.to]) {
                C d = dfs(e.to, t, min(f, e.cap));
                if (d <= EPS) continue;
                e.cap -= d;
                g[e.to][e.rev].cap += d;
                return d;
            }
            iter[v]++;
        }
        return 0;
    }

    C flow = 0;
    while (true) {
        bfs();
        if (level[t] < 0) break;
        iter[] = 0;
        while (true) {
            C f = dfs(s, t, C.max);
            if (!f) break;
            flow += f;
        }
    }

    auto mfInfo = maxFlowInfo!C();
    mfInfo.flow = flow;
    mfInfo.dual = level.map!"a == -1".array;
    return mfInfo;
}

/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/foundation.d */
// module dcomp.foundation;

static if (__VERSION__ <= 2070) {
    template fold(fun...) if (fun.length >= 1) {
        auto fold(R, S...)(R r, S seed) {
            import std.algorithm : reduce;
            static if (S.length < 2) {
                return reduce!fun(seed, r);
            } else {
                import std.typecons : tuple;
                return reduce!fun(tuple(seed), r);
            }
        }
    }

}
version (X86) static if (__VERSION__ < 2071) {
    import core.bitop : bsf, bsr, popcnt;
    int bsf(ulong v) {
        foreach (i; 0..64) {
            if (v & (1UL << i)) return i;
        }
        return -1;
    }
    int bsr(ulong v) {
        foreach_reverse (i; 0..64) {
            if (v & (1UL << i)) return i;
        }
        return -1;
    }
    int popcnt(ulong v) {
        int c = 0;
        foreach (i; 0..64) {
            if (v & (1UL << i)) c++;
        }
        return c;
    }
}
/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/container/deque.d */
// module dcomp.container.deque;

struct Deque(T, bool mayNull = true) {
    import core.exception : RangeError;
    import core.memory : GC;
    import std.range : ElementType, isInputRange;
    import std.traits : isImplicitlyConvertible;

    struct Payload {
        T *d;
        size_t st, length, cap;
        @property bool empty() const { return length == 0; }
        alias opDollar = length;
        ref inout(T) opIndex(size_t i) inout {
            version(assert) if (length <= i) throw new RangeError();
            return d[(st+i >= cap) ? (st+i-cap) : st+i];
        }
        private void expand() {
            import std.algorithm : max;
            assert(length == cap);
            auto nc = max(size_t(4), 2*cap);
            T* nd = cast(T*)GC.malloc(nc * T.sizeof);
            foreach (i; 0..length) {
                nd[i] = this[i];
            }
            d = nd; st = 0; cap = nc;
        }
        void clear() {
            st = length = 0;
        }
        void insertFront(T v) {
            if (length == cap) expand();
            if (st == 0) st += cap;
            st--; length++;
            this[0] = v;
        }
        void insertBack(T v) {
            if (length == cap) expand();
            length++;
            this[length-1] = v;
        }
        void removeFront() {
            assert(!empty, "Deque.removeFront: Deque is empty");
            st++; length--;
            if (st == cap) st = 0;
        }
        void removeBack() {
            assert(!empty, "Deque.removeBack: Deque is empty");
            length--;
        }
    }
    struct RangeT(A) {
        alias T = typeof(*(A.p));
        alias E = typeof(A.p.d[0]);
        T *p;
        size_t a, b;
        @property bool empty() const { return b <= a; }
        @property size_t length() const { return b-a; }
        @property RangeT save() { return RangeT(p, a, b); }
        @property RangeT!(const A) save() const {
            return typeof(return)(p, a, b);
        }
        alias opDollar = length;
        @property ref inout(E) front() inout { return (*p)[a]; }
        @property ref inout(E) back() inout { return (*p)[b-1]; }
        void popFront() {
            version(assert) if (empty) throw new RangeError();
            a++;
        }
        void popBack() {
            version(assert) if (empty) throw new RangeError();
            b--;
        }
        ref inout(E) opIndex(size_t i) inout { return (*p)[i]; }
        RangeT opSlice() { return this.save; }
        RangeT opSlice(size_t i, size_t j) {
            version(assert) if (i > j || a + j > b) throw new RangeError();
            return typeof(return)(p, a+i, a+j);
        }
        RangeT!(const A) opSlice() const { return this.save; }
        RangeT!(const A) opSlice(size_t i, size_t j) const {
            version(assert) if (i > j || a + j > b) throw new RangeError();
            return typeof(return)(p, a+i, a+j);
        }
    }

    alias Range = RangeT!Deque;
    alias ConstRange = RangeT!(const Deque);
    alias ImmutableRange = RangeT!(immutable Deque);

    Payload* p;
    private void I() { if (mayNull && !p) p = new Payload(); }
    private void C() const {
        version(assert) if (mayNull && !p) throw new RangeError();
    }
    static if (!mayNull) {
        @disable this();
    }

    private this(Payload* p) {
        this.p = p;
    }
    this(U)(U[] values...) if (isImplicitlyConvertible!(U, T)) {
        p = new Payload();
        foreach (v; values) {
            insertBack(v);
        }
    }

    this(Range)(Range r)
    if (isInputRange!Range &&
    isImplicitlyConvertible!(ElementType!Range, T) &&
    !is(Range == T[])) {
        p = new Payload();
        foreach (v; r) {
            insertBack(v);
        }
    }
    static Deque make() { return Deque(new Payload()); }
    @property bool havePayload() const { return (!mayNull || p); }

    @property bool empty() const { return (!havePayload || p.empty); }

    @property size_t length() const { return (havePayload ? p.length : 0); }

    alias opDollar = length;
    ref inout(T) opIndex(size_t i) inout {C; return (*p)[i]; }

    ref inout(T) front() inout {C; return (*p)[0]; }

    ref inout(T) back() inout {C; return (*p)[$-1]; }
    void clear() { if (p) p.clear(); }

    void insertFront(T v) {I; p.insertFront(v); }

    void insertBack(T v) {I; p.insertBack(v); }

    alias stableInsertBack = insertBack;

    void removeFront() {C; p.removeFront(); }

    void removeBack() {C; p.removeBack(); }

    Range opSlice() {I; return Range(p, 0, length); }
}

/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/scanner.d */
// module dcomp.scanner;

class Scanner {
    import std.stdio : File;
    import std.conv : to;
    import std.range : front, popFront, array, ElementType;
    import std.array : split;
    import std.traits : isSomeChar, isStaticArray, isArray;
    import std.algorithm : map;
    File f;
    this(File f) {
        this.f = f;
    }
    char[512] lineBuf;
    char[] line;
    private bool succ() {
        import std.range.primitives : empty, front, popFront;
        import std.ascii : isWhite;
        while (true) {
            while (!line.empty && line.front.isWhite) {
                line.popFront;
            }
            if (!line.empty) break;
            if (f.eof) return false;
            line = lineBuf[];
            f.readln(line);
        }
        return true;
    }

    private bool readSingle(T)(ref T x) {
        import std.algorithm : findSplitBefore;
        import std.string : strip;
        import std.conv : parse;
        if (!succ()) return false;
        static if (isArray!T) {
            alias E = ElementType!T;
            static if (isSomeChar!E) {

                auto r = line.findSplitBefore(" ");
                x = r[0].strip.dup;
                line = r[1];
            } else {
                auto buf = line.split.map!(to!E).array;
                static if (isStaticArray!T) {

                    assert(buf.length == T.length);
                }
                x = buf;
                line.length = 0;
            }
        } else {
            x = line.parse!T;
        }
        return true;
    }
    int read(T, Args...)(ref T x, auto ref Args args) {
        if (!readSingle(x)) return 0;
        static if (args.length == 0) {
            return 1;
        } else {
            return 1 + read(args);
        }
    }
}

/+ dub.sdl:
    name "E"
    dependency "dcomp" version=">=0.6.0"
+/

import std.stdio, std.algorithm, std.range, std.conv;
// import dcomp.foundation, dcomp.scanner;

// import dcomp.graph.maxflow;

int main() {
    auto sc = new Scanner(stdin);
    int n, m, s, t;
    sc.read(n, m, s, t); s--; t--;

    struct E {
        int to, cap, rev;
    }
    void addEdge(E[][] g, int from, int to, int cap) {
        g[from] ~= E(to, cap, g[to].length.to!int);
        g[to] ~= E(from, 0, g[from].length.to!int-1);
    }

    E[][] g = new E[][](n);

    struct E2 {
        int to, id;
    }
    E2[][] gn = new E2[][](n);

    bool[] used = new bool[n];
    int[] pa;
    bool dfs(int p, int q) {
        if (used[p]) return false;
        if (p == q) return true;
        used[p] = true;
        foreach (e; gn[p]) {
            if (dfs(e.to, q)) {
                pa ~= e.id;
                return true;
            }
        }
        return false;
    }
    bool make(int p, int q) {
        used[] = false;
        pa = new int[0];
        return dfs(p, q);
    }

    int[] U = new int[m], V = new int[m], G = new int[m];
    foreach (i; 0..m) {
        int u, v, h; sc.read(u, v, h); u--; v--;
        U[i] = u; V[i] = v; G[i] = h;
        if (h == 0) {
            addEdge(g, u, v, 100000);
            addEdge(g, v, u, 1);
        } else {
            addEdge(g, u, v, 1);
            addEdge(g, v, u, 100000);
            gn[u] ~= E2(v, i);
        }
    }
    int[] sm = new int[m];
    foreach (i; 0..m) {
        int u = U[i], v = V[i];
        if (G[i] == 0) continue;
        sm[i]++;
        if (make(v, u)) {
            foreach (d; pa) {
                sm[d]++;
            }
            continue;
        }
        make(s, u);
        foreach (d; pa) {
            sm[d]++;
        }
        make(v, t);
        foreach (d; pa) {
            sm[d]++;
        }
    }
    auto mfI = maxFlow!(int, 0)(g, s, t);

    foreach (i; 0..m) {
        int u = U[i], v = V[i];
        int di = 0;
        bool f;
        if (G[i] == 0) {
            f = !mfI.dual[v] && mfI.dual[u];
        } else {
            f = !mfI.dual[u] && mfI.dual[v];
        }
        writeln(sm[i], " ", sm[i] + (f ? 0 : 1));
    }
    return 0;
}
/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/graph/maxflow.d */
// module dcomp.graph.maxflow;

// import dcomp.container.deque;

struct maxFlowInfo(C) {
    C flow;
    bool[] dual;
}

maxFlowInfo!(C) maxFlow(C, C EPS, T)(T g, int s, int t) {
    assert(s != t);
    import std.algorithm : map;
    import std.range : array;
    import std.conv : to;
    int n = g.length.to!int;
    int[] level = new int[n];
    int[] iter = new int[n];

    void bfs() {
        level[] = -1; level[s] = 0;
        auto que = Deque!int(s);
        while (!que.empty) {
            int v = que.back; que.removeBack();
            foreach (e; g[v]) {
                if (e.cap <= EPS) continue;
                if (level[e.to] < 0) {
                    level[e.to] = level[v] + 1;
                    que.insertBack(e.to);
                }
            }
        }
    }

    C dfs(int v, int t, C f) {
        import std.algorithm : min;
        if (v == t) return f;
        auto edgeList = g[v][iter[v]..$];
        foreach (ref e; edgeList) {
            if (e.cap <= EPS) continue;
            if (level[v] < level[e.to]) {
                C d = dfs(e.to, t, min(f, e.cap));
                if (d <= EPS) continue;
                e.cap -= d;
                g[e.to][e.rev].cap += d;
                return d;
            }
            iter[v]++;
        }
        return 0;
    }

    C flow = 0;
    while (true) {
        bfs();
        if (level[t] < 0) break;
        iter[] = 0;
        while (true) {
            C f = dfs(s, t, C.max);
            if (!f) break;
            flow += f;
        }
    }

    auto mfInfo = maxFlowInfo!C();
    mfInfo.flow = flow;
    mfInfo.dual = level.map!"a == -1".array;
    return mfInfo;
}

/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/foundation.d */
// module dcomp.foundation;

static if (__VERSION__ <= 2070) {
    template fold(fun...) if (fun.length >= 1) {
        auto fold(R, S...)(R r, S seed) {
            import std.algorithm : reduce;
            static if (S.length < 2) {
                return reduce!fun(seed, r);
            } else {
                import std.typecons : tuple;
                return reduce!fun(tuple(seed), r);
            }
        }
    }

}
version (X86) static if (__VERSION__ < 2071) {
    import core.bitop : bsf, bsr, popcnt;
    int bsf(ulong v) {
        foreach (i; 0..64) {
            if (v & (1UL << i)) return i;
        }
        return -1;
    }
    int bsr(ulong v) {
        foreach_reverse (i; 0..64) {
            if (v & (1UL << i)) return i;
        }
        return -1;
    }
    int popcnt(ulong v) {
        int c = 0;
        foreach (i; 0..64) {
            if (v & (1UL << i)) c++;
        }
        return c;
    }
}
/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/container/deque.d */
// module dcomp.container.deque;

struct Deque(T, bool mayNull = true) {
    import core.exception : RangeError;
    import core.memory : GC;
    import std.range : ElementType, isInputRange;
    import std.traits : isImplicitlyConvertible;

    struct Payload {
        T *d;
        size_t st, length, cap;
        @property bool empty() const { return length == 0; }
        alias opDollar = length;
        ref inout(T) opIndex(size_t i) inout {
            version(assert) if (length <= i) throw new RangeError();
            return d[(st+i >= cap) ? (st+i-cap) : st+i];
        }
        private void expand() {
            import std.algorithm : max;
            assert(length == cap);
            auto nc = max(size_t(4), 2*cap);
            T* nd = cast(T*)GC.malloc(nc * T.sizeof);
            foreach (i; 0..length) {
                nd[i] = this[i];
            }
            d = nd; st = 0; cap = nc;
        }
        void clear() {
            st = length = 0;
        }
        void insertFront(T v) {
            if (length == cap) expand();
            if (st == 0) st += cap;
            st--; length++;
            this[0] = v;
        }
        void insertBack(T v) {
            if (length == cap) expand();
            length++;
            this[length-1] = v;
        }
        void removeFront() {
            assert(!empty, "Deque.removeFront: Deque is empty");
            st++; length--;
            if (st == cap) st = 0;
        }
        void removeBack() {
            assert(!empty, "Deque.removeBack: Deque is empty");
            length--;
        }
    }
    struct RangeT(A) {
        alias T = typeof(*(A.p));
        alias E = typeof(A.p.d[0]);
        T *p;
        size_t a, b;
        @property bool empty() const { return b <= a; }
        @property size_t length() const { return b-a; }
        @property RangeT save() { return RangeT(p, a, b); }
        @property RangeT!(const A) save() const {
            return typeof(return)(p, a, b);
        }
        alias opDollar = length;
        @property ref inout(E) front() inout { return (*p)[a]; }
        @property ref inout(E) back() inout { return (*p)[b-1]; }
        void popFront() {
            version(assert) if (empty) throw new RangeError();
            a++;
        }
        void popBack() {
            version(assert) if (empty) throw new RangeError();
            b--;
        }
        ref inout(E) opIndex(size_t i) inout { return (*p)[i]; }
        RangeT opSlice() { return this.save; }
        RangeT opSlice(size_t i, size_t j) {
            version(assert) if (i > j || a + j > b) throw new RangeError();
            return typeof(return)(p, a+i, a+j);
        }
        RangeT!(const A) opSlice() const { return this.save; }
        RangeT!(const A) opSlice(size_t i, size_t j) const {
            version(assert) if (i > j || a + j > b) throw new RangeError();
            return typeof(return)(p, a+i, a+j);
        }
    }

    alias Range = RangeT!Deque;
    alias ConstRange = RangeT!(const Deque);
    alias ImmutableRange = RangeT!(immutable Deque);

    Payload* p;
    private void I() { if (mayNull && !p) p = new Payload(); }
    private void C() const {
        version(assert) if (mayNull && !p) throw new RangeError();
    }
    static if (!mayNull) {
        @disable this();
    }

    private this(Payload* p) {
        this.p = p;
    }
    this(U)(U[] values...) if (isImplicitlyConvertible!(U, T)) {
        p = new Payload();
        foreach (v; values) {
            insertBack(v);
        }
    }

    this(Range)(Range r)
    if (isInputRange!Range &&
    isImplicitlyConvertible!(ElementType!Range, T) &&
    !is(Range == T[])) {
        p = new Payload();
        foreach (v; r) {
            insertBack(v);
        }
    }
    static Deque make() { return Deque(new Payload()); }
    @property bool havePayload() const { return (!mayNull || p); }

    @property bool empty() const { return (!havePayload || p.empty); }

    @property size_t length() const { return (havePayload ? p.length : 0); }

    alias opDollar = length;
    ref inout(T) opIndex(size_t i) inout {C; return (*p)[i]; }

    ref inout(T) front() inout {C; return (*p)[0]; }

    ref inout(T) back() inout {C; return (*p)[$-1]; }
    void clear() { if (p) p.clear(); }

    void insertFront(T v) {I; p.insertFront(v); }

    void insertBack(T v) {I; p.insertBack(v); }

    alias stableInsertBack = insertBack;

    void removeFront() {C; p.removeFront(); }

    void removeBack() {C; p.removeBack(); }

    Range opSlice() {I; return Range(p, 0, length); }
}

/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/scanner.d */
// module dcomp.scanner;

class Scanner {
    import std.stdio : File;
    import std.conv : to;
    import std.range : front, popFront, array, ElementType;
    import std.array : split;
    import std.traits : isSomeChar, isStaticArray, isArray;
    import std.algorithm : map;
    File f;
    this(File f) {
        this.f = f;
    }
    char[512] lineBuf;
    char[] line;
    private bool succ() {
        import std.range.primitives : empty, front, popFront;
        import std.ascii : isWhite;
        while (true) {
            while (!line.empty && line.front.isWhite) {
                line.popFront;
            }
            if (!line.empty) break;
            if (f.eof) return false;
            line = lineBuf[];
            f.readln(line);
        }
        return true;
    }

    private bool readSingle(T)(ref T x) {
        import std.algorithm : findSplitBefore;
        import std.string : strip;
        import std.conv : parse;
        if (!succ()) return false;
        static if (isArray!T) {
            alias E = ElementType!T;
            static if (isSomeChar!E) {

                auto r = line.findSplitBefore(" ");
                x = r[0].strip.dup;
                line = r[1];
            } else {
                auto buf = line.split.map!(to!E).array;
                static if (isStaticArray!T) {

                    assert(buf.length == T.length);
                }
                x = buf;
                line.length = 0;
            }
        } else {
            x = line.parse!T;
        }
        return true;
    }
    int read(T, Args...)(ref T x, auto ref Args args) {
        if (!readSingle(x)) return 0;
        static if (args.length == 0) {
            return 1;
        } else {
            return 1 + read(args);
        }
    }
}

/+ dub.sdl:
    name "E"
    dependency "dcomp" version=">=0.6.0"
+/

import std.stdio, std.algorithm, std.range, std.conv;
// import dcomp.foundation, dcomp.scanner;

// import dcomp.graph.maxflow;

int main() {
    auto sc = new Scanner(stdin);
    int n, m, s, t;
    sc.read(n, m, s, t); s--; t--;

    struct E {
        int to, cap, rev;
    }
    void addEdge(E[][] g, int from, int to, int cap) {
        g[from] ~= E(to, cap, g[to].length.to!int);
        g[to] ~= E(from, 0, g[from].length.to!int-1);
    }

    E[][] g = new E[][](n);

    struct E2 {
        int to, id;
    }
    E2[][] gn = new E2[][](n);

    bool[] used = new bool[n];
    int[] pa;
    bool dfs(int p, int q) {
        if (used[p]) return false;
        if (p == q) return true;
        used[p] = true;
        foreach (e; gn[p]) {
            if (dfs(e.to, q)) {
                pa ~= e.id;
                return true;
            }
        }
        return false;
    }
    bool make(int p, int q) {
        used[] = false;
        pa = new int[0];
        return dfs(p, q);
    }

    int[] U = new int[m], V = new int[m], G = new int[m];
    foreach (i; 0..m) {
        int u, v, h; sc.read(u, v, h); u--; v--;
        U[i] = u; V[i] = v; G[i] = h;
        if (h == 0) {
            addEdge(g, u, v, 100000);
        } else {
            addEdge(g, u, v, 1);
            addEdge(g, v, u, 100000);
            gn[u] ~= E2(v, i);
        }
    }
    int[] sm = new int[m];
    foreach (i; 0..m) {
        int u = U[i], v = V[i];
        if (G[i] == 0) continue;
        sm[i]++;
        if (make(v, u)) {
            foreach (d; pa) {
                sm[d]++;
            }
            continue;
        }
        make(s, u);
        foreach (d; pa) {
            sm[d]++;
        }
        make(v, t);
        foreach (d; pa) {
            sm[d]++;
        }
    }
    auto mfI = maxFlow!(int, 0)(g, s, t);
    writeln(mfI.flow);
    foreach (i; 0..m) {
        int u = U[i], v = V[i];
        int di = 0;
        bool f;
        if (G[i] == 0) {
            f = true;
        } else {
            f = !mfI.dual[u] && mfI.dual[v];
        }
        writeln(sm[i], " ", sm[i] + (f ? 0 : 1));
    }
    return 0;
}
/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/graph/maxflow.d */
// module dcomp.graph.maxflow;

// import dcomp.container.deque;

struct maxFlowInfo(C) {
    C flow;
    bool[] dual;
}

maxFlowInfo!(C) maxFlow(C, C EPS, T)(T g, int s, int t) {
    assert(s != t);
    import std.algorithm : map;
    import std.range : array;
    import std.conv : to;
    int n = g.length.to!int;
    int[] level = new int[n];
    int[] iter = new int[n];

    void bfs() {
        level[] = -1; level[s] = 0;
        auto que = Deque!int(s);
        while (!que.empty) {
            int v = que.back; que.removeBack();
            foreach (e; g[v]) {
                if (e.cap <= EPS) continue;
                if (level[e.to] < 0) {
                    level[e.to] = level[v] + 1;
                    que.insertBack(e.to);
                }
            }
        }
    }

    C dfs(int v, int t, C f) {
        import std.algorithm : min;
        if (v == t) return f;
        auto edgeList = g[v][iter[v]..$];
        foreach (ref e; edgeList) {
            if (e.cap <= EPS) continue;
            if (level[v] < level[e.to]) {
                C d = dfs(e.to, t, min(f, e.cap));
                if (d <= EPS) continue;
                e.cap -= d;
                g[e.to][e.rev].cap += d;
                return d;
            }
            iter[v]++;
        }
        return 0;
    }

    C flow = 0;
    while (true) {
        bfs();
        if (level[t] < 0) break;
        iter[] = 0;
        while (true) {
            C f = dfs(s, t, C.max);
            if (!f) break;
            flow += f;
        }
    }

    auto mfInfo = maxFlowInfo!C();
    mfInfo.flow = flow;
    mfInfo.dual = level.map!"a == -1".array;
    return mfInfo;
}

/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/foundation.d */
// module dcomp.foundation;

static if (__VERSION__ <= 2070) {
    template fold(fun...) if (fun.length >= 1) {
        auto fold(R, S...)(R r, S seed) {
            import std.algorithm : reduce;
            static if (S.length < 2) {
                return reduce!fun(seed, r);
            } else {
                import std.typecons : tuple;
                return reduce!fun(tuple(seed), r);
            }
        }
    }

}
version (X86) static if (__VERSION__ < 2071) {
    import core.bitop : bsf, bsr, popcnt;
    int bsf(ulong v) {
        foreach (i; 0..64) {
            if (v & (1UL << i)) return i;
        }
        return -1;
    }
    int bsr(ulong v) {
        foreach_reverse (i; 0..64) {
            if (v & (1UL << i)) return i;
        }
        return -1;
    }
    int popcnt(ulong v) {
        int c = 0;
        foreach (i; 0..64) {
            if (v & (1UL << i)) c++;
        }
        return c;
    }
}
/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/container/deque.d */
// module dcomp.container.deque;

struct Deque(T, bool mayNull = true) {
    import core.exception : RangeError;
    import core.memory : GC;
    import std.range : ElementType, isInputRange;
    import std.traits : isImplicitlyConvertible;

    struct Payload {
        T *d;
        size_t st, length, cap;
        @property bool empty() const { return length == 0; }
        alias opDollar = length;
        ref inout(T) opIndex(size_t i) inout {
            version(assert) if (length <= i) throw new RangeError();
            return d[(st+i >= cap) ? (st+i-cap) : st+i];
        }
        private void expand() {
            import std.algorithm : max;
            assert(length == cap);
            auto nc = max(size_t(4), 2*cap);
            T* nd = cast(T*)GC.malloc(nc * T.sizeof);
            foreach (i; 0..length) {
                nd[i] = this[i];
            }
            d = nd; st = 0; cap = nc;
        }
        void clear() {
            st = length = 0;
        }
        void insertFront(T v) {
            if (length == cap) expand();
            if (st == 0) st += cap;
            st--; length++;
            this[0] = v;
        }
        void insertBack(T v) {
            if (length == cap) expand();
            length++;
            this[length-1] = v;
        }
        void removeFront() {
            assert(!empty, "Deque.removeFront: Deque is empty");
            st++; length--;
            if (st == cap) st = 0;
        }
        void removeBack() {
            assert(!empty, "Deque.removeBack: Deque is empty");
            length--;
        }
    }
    struct RangeT(A) {
        alias T = typeof(*(A.p));
        alias E = typeof(A.p.d[0]);
        T *p;
        size_t a, b;
        @property bool empty() const { return b <= a; }
        @property size_t length() const { return b-a; }
        @property RangeT save() { return RangeT(p, a, b); }
        @property RangeT!(const A) save() const {
            return typeof(return)(p, a, b);
        }
        alias opDollar = length;
        @property ref inout(E) front() inout { return (*p)[a]; }
        @property ref inout(E) back() inout { return (*p)[b-1]; }
        void popFront() {
            version(assert) if (empty) throw new RangeError();
            a++;
        }
        void popBack() {
            version(assert) if (empty) throw new RangeError();
            b--;
        }
        ref inout(E) opIndex(size_t i) inout { return (*p)[i]; }
        RangeT opSlice() { return this.save; }
        RangeT opSlice(size_t i, size_t j) {
            version(assert) if (i > j || a + j > b) throw new RangeError();
            return typeof(return)(p, a+i, a+j);
        }
        RangeT!(const A) opSlice() const { return this.save; }
        RangeT!(const A) opSlice(size_t i, size_t j) const {
            version(assert) if (i > j || a + j > b) throw new RangeError();
            return typeof(return)(p, a+i, a+j);
        }
    }

    alias Range = RangeT!Deque;
    alias ConstRange = RangeT!(const Deque);
    alias ImmutableRange = RangeT!(immutable Deque);

    Payload* p;
    private void I() { if (mayNull && !p) p = new Payload(); }
    private void C() const {
        version(assert) if (mayNull && !p) throw new RangeError();
    }
    static if (!mayNull) {
        @disable this();
    }

    private this(Payload* p) {
        this.p = p;
    }
    this(U)(U[] values...) if (isImplicitlyConvertible!(U, T)) {
        p = new Payload();
        foreach (v; values) {
            insertBack(v);
        }
    }

    this(Range)(Range r)
    if (isInputRange!Range &&
    isImplicitlyConvertible!(ElementType!Range, T) &&
    !is(Range == T[])) {
        p = new Payload();
        foreach (v; r) {
            insertBack(v);
        }
    }
    static Deque make() { return Deque(new Payload()); }
    @property bool havePayload() const { return (!mayNull || p); }

    @property bool empty() const { return (!havePayload || p.empty); }

    @property size_t length() const { return (havePayload ? p.length : 0); }

    alias opDollar = length;
    ref inout(T) opIndex(size_t i) inout {C; return (*p)[i]; }

    ref inout(T) front() inout {C; return (*p)[0]; }

    ref inout(T) back() inout {C; return (*p)[$-1]; }
    void clear() { if (p) p.clear(); }

    void insertFront(T v) {I; p.insertFront(v); }

    void insertBack(T v) {I; p.insertBack(v); }

    alias stableInsertBack = insertBack;

    void removeFront() {C; p.removeFront(); }

    void removeBack() {C; p.removeBack(); }

    Range opSlice() {I; return Range(p, 0, length); }
}

/* IMPORT /home/yosupo/Program/dcomp/source/dcomp/scanner.d */
// module dcomp.scanner;

class Scanner {
    import std.stdio : File;
    import std.conv : to;
    import std.range : front, popFront, array, ElementType;
    import std.array : split;
    import std.traits : isSomeChar, isStaticArray, isArray;
    import std.algorithm : map;
    File f;
    this(File f) {
        this.f = f;
    }
    char[512] lineBuf;
    char[] line;
    private bool succ() {
        import std.range.primitives : empty, front, popFront;
        import std.ascii : isWhite;
        while (true) {
            while (!line.empty && line.front.isWhite) {
                line.popFront;
            }
            if (!line.empty) break;
            if (f.eof) return false;
            line = lineBuf[];
            f.readln(line);
        }
        return true;
    }

    private bool readSingle(T)(ref T x) {
        import std.algorithm : findSplitBefore;
        import std.string : strip;
        import std.conv : parse;
        if (!succ()) return false;
        static if (isArray!T) {
            alias E = ElementType!T;
            static if (isSomeChar!E) {

                auto r = line.findSplitBefore(" ");
                x = r[0].strip.dup;
                line = r[1];
            } else {
                auto buf = line.split.map!(to!E).array;
                static if (isStaticArray!T) {

                    assert(buf.length == T.length);
                }
                x = buf;
                line.length = 0;
            }
        } else {
            x = line.parse!T;
        }
        return true;
    }
    int read(T, Args...)(ref T x, auto ref Args args) {
        if (!readSingle(x)) return 0;
        static if (args.length == 0) {
            return 1;
        } else {
            return 1 + read(args);
        }
    }
}

import std.conv, std.functional, std.range, std.stdio, std.string;
import std.algorithm, std.array, std.bigint, std.bitmanip, std.complex, std.container, std.math, std.mathspecial, std.numeric, std.regex, std.typecons;
import core.bitop;

class EOFException : Throwable { this() { super("EOF"); } }
string[] tokens;
string readToken() { for (; tokens.empty; ) { if (stdin.eof) { throw new EOFException; } tokens = readln.split; } auto token = tokens.front; tokens.popFront; return token; }
int readInt() { return readToken.to!int; }
long readLong() { return readToken.to!long; }
real readReal() { return readToken.to!real; }

bool chmin(T)(ref T t, in T f) { if (t > f) { t = f; return true; } else { return false; } }
bool chmax(T)(ref T t, in T f) { if (t < f) { t = f; return true; } else { return false; } }

int binarySearch(alias pred, T)(in T[] as) { int lo = -1, hi = cast(int)(as.length); for (; lo + 1 < hi; ) { const mid = (lo + hi) >> 1; (unaryFun!pred(as[mid]) ? hi : lo) = mid; } return hi; }
int lowerBound(T)(in T[] as, T val) { return as.binarySearch!(a => (a >= val)); }
int upperBound(T)(in T[] as, T val) { return as.binarySearch!(a => (a > val)); }

class MaxFlow(Capa) {
  enum Capa wEPS = 0;
  enum Capa wINF = 10^^9;
  int n, m;
  int[][] g;
  int[] zu;
  Capa[] capa;
  Capa tof;
  int[] lev, see, que;
  this(int n) {
    this.n = n; m = 0; g = new int[][n]; zu = []; capa = [];
    lev = new int[n]; see = new int[n]; que = new int[n];
  }
  void addEdge(int u, int v, Capa w0, Capa w1 = 0) {
    g[u] ~= m; zu ~= v; capa ~= w0; ++m;
    g[v] ~= m; zu ~= u; capa ~= w1; ++m;
  }
  Capa augment(int src, int ink, Capa flo) {
    if (src == ink) return flo;
    foreach (i; g[src][see[src] .. $]) {
      if (capa[i] > wEPS && lev[src] < lev[zu[i]]) {
        Capa f = augment(zu[i], ink, min(flo, capa[i]));
        if (f > wEPS) { capa[i] -= f; capa[i ^ 1] += f; return f; }
      }
      ++see[src];
    }
    return 0;
  }
  bool dinic(int src, int ink, Capa flo = wINF) {
    for (tof = 0; tof + wEPS < flo; ) {
      int[] q;
      lev[] = -1;
     dinicBFS:
      for (lev[src] = 0, q ~= src; !q.empty; ) {
        int u = q.front; q.popFront;
        foreach (i; g[u]) {
          int v = zu[i];
          if (capa[i] > wEPS && lev[v] == -1) {
            lev[v] = lev[u] + 1; q ~= v;
            if (v == ink) break dinicBFS;
          }
        }
      }
      if (lev[ink] == -1) return false;
      see[] = 0;
      for (; ; ) {
        Capa f = augment(src, ink, flo - tof);
        if (f <= wEPS) break;
        tof += f;
      }
    }
    return true;
  }
}

void main() {
  try {
    for (; ; ) {
      const N = readInt();
      const M = readInt();
      const S = readInt() - 1;
      const T = readInt() - 1;
      auto A = new int[M];
      auto B = new int[M];
      auto G = new int[M];
      foreach (i; 0 .. M) {
        A[i] = readInt() - 1;
        B[i] = readInt() - 1;
        G[i] = readInt();
      }

      auto mf0 = new MaxFlow!int(N);
      foreach (i; 0 .. M) {
        if (G[i] == 1) {
          mf0.addEdge(A[i], B[i], 1);
          mf0.addEdge(B[i], A[i], M + 1);
        } else {
          mf0.addEdge(A[i], B[i], M + 1);
        }
      }
      mf0.dinic(S, T);
      assert(mf0.tof <= M);
      auto d = new bool[][](N, N);
      foreach (u; 0 .. N) {
        d[u][u] = true;
      }
      foreach (i; 0 .. mf0.m) {
        if (mf0.capa[i] > 0) {
          d[mf0.zu[i ^ 1]][mf0.zu[i]] = true;
        }
      }
      foreach (w; 0 .. N) foreach (u; 0 .. N) foreach (v; 0 .. N) {
        if (d[u][w] && d[w][v]) {
          d[u][v] = true;
        }
      }

      auto ex = new int[N];
      foreach (i; 0 .. M) {
        if (G[i] == 1) {
          --ex[A[i]];
          ++ex[B[i]];
        }
      }
      debug {
        writeln("ex = ", ex);
      }
      auto mf1 = new MaxFlow!int(N);
      auto ids1 = new int[M];
      ids1[] = -1;
      foreach (u; 0 .. N) {
        if (u != S && u != T) {
          if (ex[u] < 0) {
            mf1.addEdge(S, u, -ex[u]);
          } else {
            mf1.addEdge(u, T, ex[u]);
          }
        }
      }
      foreach (i; 0 .. M) {
        if (G[i] == 1) {
          ids1[i] = mf1.m;
          mf1.addEdge(A[i], B[i], M);
        }
      }
      mf1.dinic(S, T);

      writeln(mf0.tof);
      foreach (i; 0 .. M) {
        int f, c;
        if (G[i] == 1) {
          f = 1 + mf1.capa[ids1[i] ^ 1];
          c = (d[S][A[i]] && !d[S][B[i]]) ? f : (f + 1);
        } else {
          f = c = 0;
        }
        writeln(f, " ", c);
      }
    }
  } catch (EOFException e) {
  }
}


Everyone knows that agents in Valorant decide, who will play as attackers, and who will play as defenders. To do that Raze and Breach decided to play $$$t$$$ matches of a digit game...In each of $$$t$$$ matches of the digit game, a positive integer is generated. It consists of $$$n$$$ digits. The digits of this integer are numerated from $$$1$$$ to $$$n$$$ from the highest-order digit to the lowest-order digit. After this integer is announced, the match starts.Agents play in turns. Raze starts. In one turn an agent can choose any unmarked digit and mark it. Raze can choose digits on odd positions, but can not choose digits on even positions. Breach can choose digits on even positions, but can not choose digits on odd positions. The match ends, when there is only one unmarked digit left. If the single last digit is odd, then Raze wins, else Breach wins.It can be proved, that before the end of the match (for every initial integer with $$$n$$$ digits) each agent has an ability to make a turn, i.e. there is at least one unmarked digit, that stands on a position of required parity.For each of $$$t$$$ matches find out, which agent wins, if both of them want to win and play optimally.

First line of input contains an integer $$$t$$$ $$$(1 \le t \le 100)$$$  — the number of matches. The first line of each match description contains an integer $$$n$$$ $$$(1 \le n \le 10^3)$$$  — the number of digits of the generated number. The second line of each match description contains an $$$n$$$-digit positive integer without leading zeros.

For each match print $$$1$$$, if Raze wins, and $$$2$$$, if Breach wins.

NoteIn the first match no one can make a turn, the only digit left is $$$2$$$, it's even, so Breach wins.In the second match the only digit left is $$$3$$$, it's odd, so Raze wins.In the third match Raze can mark the last digit, after that Breach can only mark $$$0$$$. $$$1$$$ will be the last digit left, it's odd, so Raze wins.In the fourth match no matter how Raze plays, Breach can mark $$$9$$$, and in the end there will be digit $$$0$$$. It's even, so Breach wins.

import std.stdio, std.conv, std.functional, std.string, std.algorithm;
import std.container, std.range, std.typecons, std.bigint, std.numeric, std.math, std.random;

alias ll = long;
alias rbt = redBlackTree;
alias tup = Tuple!(long, long);
static string[] inp;
T rd(T = long)(){while(!inp.length) inp = readln.chomp.split; string a = inp[0]; inp.popFront; return a.to!T;}
T[] rdarr(T = long)(T fix = 0){ auto r = readln.chomp.split.to!(T[]); r[] += fix; return r; }

void solve(){
    int n;
    n = rd!int;
    dchar[] arr = rd!(dchar[]);
    int isodd = 0;
    if(n % 2){
        foreach(i; 0..n){
            // Odds
            if(i % 2 == 0 && (arr[i] - '0') % 2){
                isodd = 1;
                break;
            }
        }
        if(isodd){
            writeln(1);
        }else{
            writeln(2);
        }
    }else{
        foreach(i; 0..n){
            // Even
            if(i % 2 == 1 && (arr[i] - '0') % 2 == 0){
                isodd = 1;
                break;
            }
        }
        if(isodd){
            writeln(2);
        }else{
            writeln(1);
        }
    }
}

void main(){
    long t = 1;
    t = rd;
    while(t--) solve();
    stdout.flush;
}

// Vicfred
// https://codeforces.com/contest/1419/problem/A
// greedy
import std.stdio;

void main() {
    int t;
    readf("%s\n", &t);

    int n;
    string s;
    while(t--) {
        readf("%s\n", &n);
        readf("%s\n", &s);

        int even, odd;
        even = 0;
        odd = 0;

        for(int i = 1; i <= n; i++) {
            if(i % 2 == 1) {
                odd += (s[i - 1] - '0') % 2;
            } else {
                even += ((s[i - 1] - '0') + 1) % 2;
            }
        }

        //writefln("%s %s", even, odd);

        int winner;
        if(n % 2 == 1) {
            if(odd > 0)
                winner = 1;
            else
                winner = 2;
            winner.writeln;
            continue;
        }

        if(even > 0)
            winner = 2;
        else
            winner = 1;
        winner.writeln;
    }
}

import std.stdio, std.algorithm, std.conv, std.array, std.string, std.math, std.typecons, std.numeric, std.container, std.range;

void main()
{
    auto T = readln.chomp.to!int;
    foreach (_; 0..T) {
        auto N = readln.chomp.to!int;
        auto D = readln.chomp;
        if (N%2 == 1) {
            for (int i = 0; i < N; i+=2) {
                if ((D[i]-'0')%2 == 1) {
                    writeln(1);
                    goto end;
                }
            }
            writeln(2);
        } else {
            for (int i = 1; i < N; i += 2) {
                if ((D[i]-'0')%2 == 0) {
                    writeln(2);
                    goto end;
                }
            }
            writeln(1);
        }
        end:
    }
}

import std.stdio, std.algorithm, std.conv, std.array, std.string, std.math, std.typecons, std.numeric, std.container, std.range;

void get(Args...)(ref Args args)
{
    import std.traits, std.meta, std.typecons;

    static if (Args.length == 1) {
        alias Arg = Args[0];

        static if (isArray!Arg) {
            args[0] = readln.split.to!Arg;
        } else static if (isTuple!Arg) {
            auto input = readln.split;
            static foreach (i; 0..Fields!Arg.length) {
                args[0][i] = input[i].to!(Fields!Arg[i]);
            }
        } else {
            args[0] = readln.chomp.to!Arg;
        }
    } else {
        auto input = readln.split;
        assert(input.length == Args.length);

        static foreach (i; 0..Args.length) {
            args[i] = input[i].to!(Args[i]);
        }
    }
}

void get_lines(Args...)(size_t N, ref Args args)
{
    import std.traits, std.range;

    static foreach (i; 0..Args.length) {
        static assert(isArray!(Args[i]));
        args[i].length = N;
    }

    foreach (i; 0..N) {
        static if (Args.length == 1) {
            get(args[0][i]);
        } else {
            auto input = readln.split;
            static foreach (j; 0..Args.length) {
                args[j][i] = input[j].to!(ElementType!(Args[j]));
            }
        }
    }
}

void main()
{
    int T; get(T);
    foreach (_; 0..T) {
        int N; get(N);
        auto D = readln.chomp;
        if (N%2 == 1) {
            for (int i = 0; i < N; i+=2) {
                if ((D[i]-'0')%2 == 1) {
                    writeln(1);
                    goto end;
                }
            }
            writeln(2);
        } else {
            for (int i = 1; i < N; i += 2) {
                if ((D[i]-'0')%2 == 0) {
                    writeln(2);
                    goto end;
                }
            }
            writeln(1);
        }
        end:
    }
}

import std.stdio, std.conv, std.functional, std.string;
import std.algorithm, std.array, std.container, std.range, std.typecons;
import std.bigint, std.numeric, std.math, std.random;
import core.bitop;

string FMT_F = "%.10f";

static File _f;
void file_io(string fn) { _f = File(fn, "r"); }
static string[] s_rd;
T _RD(T = long)() { while(!s_rd.length) s_rd = readln.chomp.split; string res = s_rd[0]; s_rd.popFront; return res.to!T; }
T _RD(T = long)(File f) { while(!s_rd.length) s_rd = f.readln.chomp.split; string res = s_rd[0]; s_rd.popFront; return res.to!T; }
T[] _RDA(T = long)(T fix = 0) { auto r = readln.chomp.split.to!(T[]); r[] += fix; return r; }
T[] _RDA(T = long)(File f, T fix = 0) { auto r = f.readln.chomp.split.to!(T[]); r[] += fix; return r; }
T RD(T = long)() { if (_f.isOpen) return _RD!T(_f); else return _RD!T; }
T[] RDA(T = long)(T fix = 0) { if (_f.isOpen)  return _RDA!T(_f, fix); else return _RDA!T(fix); }

size_t[] MAKE_IDX(alias less = "a < b", Range)(Range range) { auto idx = new size_t[](range.length); makeIndex!(less)(range, idx); return idx;}
size_t MIN_POS(alias less = "a < b", Range)(Range range) { auto r = minPos!(less)(range); return range.length - r.length; }

void chmin(T)(ref T x, T y) { x = min(x, y); } void chmax(T)(ref T x, T y) { x = max(x, y); }
bool inside(T)(T x, T b, T e) { return x >= b && x < e; }
T lcm(T)(T x, T y) { return x * (y / gcd(x, y)); }

//long mod = 10^^9 + 7;
long mod = 998_244_353;
//long mod = 1_000_003;
void moda(T)(ref T x, T y) { x = (x + y) % mod; }
void mods(T)(ref T x, T y) { x = ((x + mod) - (y % mod)) % mod; }
void modm(T)(ref T x, T y) { x = (x * y) % mod; }
void modpow(T)(ref T x, T y) { if (!y) { x = 1; return; } auto t = x; x.modpow(y>>1); x.modm(x); if (y&1) x.modm(t); }
void modd(T)(ref T x, T y) { y.modpow(mod - 2); x.modm(y); }

void main()
{
	auto t = RD!int;
	auto ans = new int[](t);
	foreach (ti; 0..t)
	{
		auto n = RD!int;
		auto s = RD!string;
		if (n % 2)
		{
			bool ok;
			foreach (i; 0..n)
			{
				if (i % 2 == 0)
				{
					if ((s[i]-'0') % 2)
						ok = true;
				}
			}
			ans[ti] = ok ? 1 : 2;
		}
		else
		{
			bool ok;
			foreach (i; 0..n)
			{
				if (i % 2)
				{
					if ((s[i]-'0') % 2 == 0)
						ok = true;
				}
			}
			ans[ti] = ok ? 2 : 1;
		}
	}

	foreach (e; ans)
		writeln(e);
	stdout.flush;
	debug readln;
}

import std.stdio, std.conv, std.functional, std.string, std.algorithm;
import std.container, std.range, std.typecons, std.bigint, std.numeric, std.math, std.random;

alias ll = long;
alias rbt = redBlackTree;
alias tup = Tuple!(long, long);
static string[] inp;
T rd(T = long)(){while(!inp.length) inp = readln.chomp.split; string a = inp[0]; inp.popFront; return a.to!T;}
T[] rdarr(T = long)(T fix = 0){ auto r = readln.chomp.split.to!(T[]); r[] += fix; return r; }

void solve(){
    int n;
    n = rd!int;
    dchar[] arr = rd!(dchar[]);
    int isodd = 0;
    foreach(i; 0..n){
        // Odds
        if(i % 2 == 0 && (arr[i] - '0') % 2){
            isodd = 1;
            break;
        }
    }
    if(isodd){
        writeln(1);
    }else{
        writeln(2);
    }
}

void main(){
    long t = 1;
    t = rd;
    while(t--) solve();
    stdout.flush;
}

import std.stdio, std.algorithm, std.conv, std.array, std.string, std.math, std.typecons, std.numeric, std.container, std.range;

void get(Args...)(ref Args args)
{
    import std.traits, std.meta, std.typecons;

    static if (Args.length == 1) {
        alias Arg = Args[0];

        static if (isArray!Arg) {
            args[0] = readln.split.to!Arg;
        } else static if (isTuple!Arg) {
            auto input = readln.split;
            static foreach (i; 0..Fields!Arg.length) {
                args[0][i] = input[i].to!(Fields!Arg[i]);
            }
        } else {
            args[0] = readln.chomp.to!Arg;
        }
    } else {
        auto input = readln.split;
        assert(input.length == Args.length);

        static foreach (i; 0..Args.length) {
            args[i] = input[i].to!(Args[i]);
        }
    }
}

void get_lines(Args...)(size_t N, ref Args args)
{
    import std.traits, std.range;

    static foreach (i; 0..Args.length) {
        static assert(isArray!(Args[i]));
        args[i].length = N;
    }

    foreach (i; 0..N) {
        static if (Args.length == 1) {
            get(args[0][i]);
        } else {
            auto input = readln.split;
            static foreach (j; 0..Args.length) {
                args[j][i] = input[j].to!(ElementType!(Args[j]));
            }
        }
    }
}

void main()
{
    int T; get(T);
    foreach (_; 0..T) {
        int N; get(N);
        string D; get(D);
        if (N%2 == 1) {
            for (int i = 0; i < N; i+=2) {
                if ((D[i]-'0')%2 == 1) {
                    writeln(1);
                    goto end;
                }
            }
            writeln(2);
        } else {
            for (int i = 1; i < N; i += 2) {
                if ((D[i]-'0')%2 == 0) {
                    writeln(2);
                    goto end;
                }
            }
            writeln(1);
        }
        end:
    }
}


You are given $$$n$$$ strings $$$s_1, s_2, \dots, s_n$$$ of length at most $$$\mathbf{8}$$$. For each string $$$s_i$$$, determine if there exist two strings $$$s_j$$$ and $$$s_k$$$ such that $$$s_i = s_j + s_k$$$. That is, $$$s_i$$$ is the concatenation of $$$s_j$$$ and $$$s_k$$$. Note that $$$j$$$ can be equal to $$$k$$$.Recall that the concatenation of strings $$$s$$$ and $$$t$$$ is $$$s + t = s_1 s_2 \dots s_p t_1 t_2 \dots t_q$$$, where $$$p$$$ and $$$q$$$ are the lengths of strings $$$s$$$ and $$$t$$$ respectively. For example, concatenation of "code" and "forces" is "codeforces".

The first line contains a single integer $$$t$$$ ($$$1 \leq t \leq 10^4$$$) — the number of test cases. The first line of each test case contains a single integer $$$n$$$ ($$$1 \leq n \leq 10^5$$$) — the number of strings. Then $$$n$$$ lines follow, the $$$i$$$-th of which contains non-empty string $$$s_i$$$ of length at most $$$\mathbf{8}$$$, consisting of lowercase English letters. Among the given $$$n$$$ strings, there may be equal (duplicates). The sum of $$$n$$$ over all test cases doesn't exceed $$$10^5$$$.

For each test case, output a binary string of length $$$n$$$. The $$$i$$$-th bit should be $$$\texttt{1}$$$ if there exist two strings $$$s_j$$$ and $$$s_k$$$ where $$$s_i = s_j + s_k$$$, and $$$\texttt{0}$$$ otherwise. Note that $$$j$$$ can be equal to $$$k$$$.

3

5

abab

ab

abc

abacb

c

3

x

xx

xxx

8

codeforc

es

codes

cod

forc

forces

e

code

NoteIn the first test case, we have the following:   $$$s_1 = s_2 + s_2$$$, since $$$\texttt{abab} = \texttt{ab} + \texttt{ab}$$$. Remember that $$$j$$$ can be equal to $$$k$$$.  $$$s_2$$$ is not the concatenation of any two strings in the list.  $$$s_3 = s_2 + s_5$$$, since $$$\texttt{abc} = \texttt{ab} + \texttt{c}$$$.  $$$s_4$$$ is not the concatenation of any two strings in the list.  $$$s_5$$$ is not the concatenation of any two strings in the list.  Since only $$$s_1$$$ and $$$s_3$$$ satisfy the conditions, only the first and third bits in the answer should be $$$\texttt{1}$$$, so the answer is $$$\texttt{10100}$$$.

import std.algorithm;
import std.conv;
import std.range;
import std.stdio;
import std.string;

void main ()
{
	auto tests = readln.strip.to !(int);
	foreach (test; 0..tests)
	{
		auto n = readln.strip.to !(int);
		auto s = new string [n];
		foreach (ref t; s)
		{
			t = readln.strip;
		}

		bool [string] b;
		foreach (ref t; s)
		{
			b[t] = true;
		}

		auto answer = new bool [n];
		foreach (i, ref t; s)
		{
			foreach (lo; 1..t.length)
			{
				auto hi = t.length - lo;
				if (t[0..lo] in b && t[lo..$] in b)
				{
					answer[i] = true;
				}
			}
		}

		writefln !("%(%d%)") (answer);
	}
}

import std.algorithm;
import std.conv;
import std.range;
import std.stdio;
import std.string;

immutable int limit = 9;

void main ()
{
	auto tests = readln.strip.to !(int);
	foreach (test; 0..tests)
	{
		auto n = readln.strip.to !(int);
		auto s = new string [n];
		foreach (ref t; s)
		{
			t = readln.strip;
		}

		auto b = new bool [string] [limit];
		foreach (ref t; s)
		{
			b[t.length][t] = true;
		}

		auto answer = new bool [n];
		foreach (i, ref t; s)
		{
			foreach (lo; 1..t.length)
			{
				auto hi = t.length - lo;
				if (t[0..lo] in b[lo] && t[lo..$] in b[hi])
				{
					answer[i] = true;
				}
			}
		}

		writefln !("%(%d%)") (answer);
	}
}


In the beginning of the new year Keivan decided to reverse his name. He doesn't like palindromes, so he changed Naviek to Navick.He is too selfish, so for a given n he wants to obtain a string of n characters, each of which is either 'a', 'b' or 'c', with no palindromes of length 3 appearing in the string as a substring. For example, the strings "abc" and "abca" suit him, while the string "aba" doesn't. He also want the number of letters 'c' in his string to be as little as possible.

The first line contains single integer n (1 ≤ n ≤ 2·105) — the length of the string.

Print the string that satisfies all the constraints. If there are multiple answers, print any of them.

NoteA palindrome is a sequence of characters which reads the same backward and forward.

module main;
import std.bigint,std.container,std.array,std.algorithm,std.conv,
	std.functional,std.math,std.numeric,std.string,std.range,std.complex,
	std.typecons,std.regex,std.random,std.uni,std.traits,std.stdio,std.variant,
	core.stdc.stdio : freopen;
alias RedBlackTree Rbt;alias redBlackTree rbt;alias BigInt big;
alias pair(X,Y)=Tuple!(X,"fi",Y,"se");alias long lint;const int mod=1000000007;
alias pair!(int,int) pii;alias pair!(long,long) pll;alias BoyerMooreFinder strfind;
alias make_pair mp;alias binary_search bins;alias gcd=std.numeric.gcd;
pure nothrow
{
	T lcm (T)(auto ref T a,auto ref T b){return a/gcd(a,b)*b;}
	pair!(X,Y) make_pair(X,Y)(in X x_,in Y y_){return tuple!(X,"fi",Y,"se")(x_,y_);}
	big gcd(big a,big b){while(b){a%=b;swap(a,b);}return a;}
	@property X sqr(X)(in X a_) {return a_*a_;}
	@property X cub(X)(in X a_) {return a_*a_*a_;}
	int sz(T)(T a) if(hasLength!T){return cast(int)a.length;}
	size_t lowb(T,X)(in T a,auto ref X g)
	 if(isRandomAccessRange!T && hasLength!T && is(typeof(g<a[0])==bool))
	{
		size_t l=0,r=a.length;
		while(r-l>1)
		{
			auto m=(l+r)>>1;
			if(!(a[m]<g))r=m;
			else l=m;
		}
		return (!(a[l]<g))?l:r;
	}
	size_t upb(T,X)(in T a,auto ref X g)
	 if(isRandomAccessRange!T && hasLength!T && is(typeof(g<a[0])==bool))
	{
		auto pos=lowb(a,g);
		if(pos==a.length)return pos;
		else return a[pos]==g?pos+1:pos;
	}
	size_t binf(T,X)(in T a,auto ref X g)
	 if(isRandomAccessRange!T && hasLength!T && is(typeof(g<a[0])==bool))
	{
		auto pos=lowb(a,g);
		return (g==a[pos])?pos:a.length;
	}
	bool binary_search(T,X)(in T a,auto ref X g)
	 if(isRandomAccessRange!T && hasLength!T && is(typeof(g<a[0])==bool))
	{
		return binf(a,g)!=a.length;
	}
}
void putarr(X)(in X a,in char ch=' '){foreach(const ref i;a)write(i,ch);}
void putarr(X)(in X a,in size_t n=a.length,in size_t m=a.front.length)
{
	foreach(i;0..n)
	{
		foreach(j;0..m)write(a[i][j],' ');
		writeln;
	}
}
bool getarr(X)(X a,in size_t n)
{bool b=1; foreach(ref i;a[0..n])b=input(&i);return b;}
bool input(T...)(auto ref T ptrs) if (ptrs.length > 0)
{
	size_t s=0;
	foreach(ref e;ptrs)
	{
		if(isSomeChar!(Unqual!(typeof(e))))s+=readf(" %c",&e);
		else s+=readf(" %s",&e);
	}
	return s==ptrs.length;
}
bool read(T...)(auto ref T ptrs) if (ptrs.length > 0)
{
	size_t s=0;
	foreach(ref e;ptrs)
	{
		if(isSomeChar!(Unqual!(typeof(e))))s+=readf(" %c",&e);
		else s+=readf(" %s",&e);
	}
	readln;
	return s==ptrs.length;
}
nothrow auto inf(in char* name) {return freopen(name,"r",core.stdc.stdio.stdin);}
nothrow auto ouf(in char* name) {return freopen(name,"w",core.stdc.stdio.stdout);}
@property auto arread(T)(){return readln.splitter.map!(to!(T)).array;}
//foreach_reverse isAlpha isNumber isWhite isLower isUpper toLower toUpper mixin
//cumulativeFold return static heapify join filter foreach
//------------------------------------------program beginning--------------------------

void main()
{
	version(ONLINE_JUDGE){}
	else
	{
		assert(freopen("input.txt","r",core.stdc.stdio.stdin));
		assert(freopen("output.txt","w",core.stdc.stdio.stdout));
	}
	int n;
loop:while(read(n))
	{
		foreach(i;0..n)
		{
			if(i%4<2)write('a');
			else write('b');
		}
	}
}