/+ 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) {
  }
}


// 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;
    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;
}

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:
    }
}


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);
	}
}

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.stdio;
import std.uni;
import std.conv;
import std.container;
import std.functional;
import std.algorithm;
import std.array;
import std.typecons;
import std.range;
import std.numeric;
import std.math;
import std.traits;

void main(string[] args)
{
  auto a00 = next!long;
  auto a01 = next!long;
  auto a10 = next!long;
  auto a11 = next!long;
  auto c0 = cast(long) (round((real(1.0) + sqrt(real(1.0) + 8.0 * a00)) / real(2.0)));
  auto c0p = cast(long) (round((real(1.0) - sqrt(real(1.0) + 8.0 * a00)) / real(2.0)));
  auto c1 = cast(long) (round((real(1.0) + sqrt(real(1.0) + 8.0 * a11)) / real(2.0)));
  auto c1p = cast(long) (round((real(1.0) - sqrt(real(1.0) + 8.0 * a11)) / real(2.0)));
  if (((c0 - 1) * c0) / 2 != a00 || ((c1 - 1) * c1) / 2 != a11)
    return writeln("Impossible");

  string tryValues(long c0, long c1)
  {
    if (c0 < 0 || c1 < 0) return null;
    if (a00 + a01 + a10 + a11 != ((c0 + c1 - 1) * (c0 + c1) / 2))
      return null;
    if (c1 == 0)
      return "0".repeat(cast(int)c0).join;
    auto mul0 = a01 / c1;
    auto res0 = a01 % c1;
    assert(mul0 <= c0);
    if (res0 == 0)
      {
	return chain("0".repeat(cast(int)mul0),
		     "1".repeat(cast(int)c1),
		     "0".repeat(cast(int)(c0 - mul0))).join;
      }
    else
      {
	return chain("0".repeat(cast(int)mul0),
		     "1".repeat(cast(int)(c1 - res0)),
		     only("0"),
		     "1".repeat(cast(int)res0),
		     "0".repeat(cast(int)(c0 - mul0 - 1))).join;
      }
  }
  foreach(tc0; [c0, c0p])
    foreach(tc1; [c1, c1p])
      if (string res = tryValues(tc0, tc1))
	return res.writeln;
  writeln("Impossible");

}

static this()
{
  popChar;
}

struct Any(T)
{
  static auto read()
  {
    static if (is(T == char))
      {
	auto res = frontChar;
	popChar;
	return res;
      }
  }
}

struct Unsigned(T)
{
  auto read()
  {
    auto res = T(0);
    while(!frontChar.isDigit) popChar;
    do
      {
	res = res * T(10) + T(frontChar - '0');
	popChar;
      }
    while(frontChar.isDigit);
    return res;
  }
}

struct Matrix(T, alias rows, alias cols)
{
  T[][] _cell;
  alias _cell this;
  // Ring constructor
  this(int n)
  {
    final switch(n)
      {
      case 0:
	_cell = new T[][](rows, cols);
	break;
      case 1:
	debug assert(rows == cols);
	_cell = new T[][](rows, cols);
	foreach(i; 0 .. rows)
	  _cell[i][i] = T(1);
	break;
      }
  }
  // Copy constructor
  this(Matrix!(T, rows, cols) other)
  {
    _cell = new T[][](rows, cols);
    foreach(i; 0 .. rows)
      _cell[i][] = other[i][];
  }
  auto opBinary(string op)(Matrix!(T, rows, cols) other)
       if (op == "+" || op == "-")
	 {
	   auto res = Matrix!(T, rows, cols)(0);
	   foreach(i; 0 .. rows)
	     foreach(j; 0 .. cols)
	       res[i][j] = mixin(q{this[i][j]}, op, q{other[i][j]});
	   return res;
	 }
  auto opBinary(string op, alias otherRows, alias otherCols)(Matrix!(T, otherRows, otherCols) other)
       if (op == "*")
	 {
	   debug assert(cols == otherRows);
	   auto res = Matrix!(T, rows, otherCols)(0);
	   foreach(i; 0 .. rows)
	     foreach(k; 0 .. cols)
	       foreach(j; 0 .. otherCols)
		 res[i][j] += this[i][k] * other[k][j];
	   return res;
	 }
  ref auto opOpAssign(string op)(Matrix!(T, rows, cols) other)
  {
    static if (op == "*")
      {
	debug assert(rows == cols);
	alias n = rows;
	auto res = Matrix!(T, n, n)(0);
	T factor;
	foreach(i; 0 .. n)
	  foreach(k; 0 .. n)
	    foreach(j; 0 .. n)
	      res[i][j] += this[i][k] * other[k][j];
	foreach(i; 0 .. n)
	  this[i][] = res[i][];
      }
    else
      {
	foreach(i; 0 .. rows)
	  foreach(j; 0 .. cols)
	    mixin(q{this[i][j] }, text(op, q{=}), q{ other[i][j];});
      }
    return this;
  }
  mixin powMethod;
}

struct Mod(T, alias mod)
{
  alias This = Mod!(T, mod);
  T _rep;
  this(T t)
  {
    _rep = t % mod;
    if (_rep < 0) _rep += mod;
  }
  static auto plain(T t)
  {
    auto res = Mod!(T, mod)();
    res._rep = t;
    return res;
  }
  static auto fromPositive(T t)
  {
    pragma(inline, true);
    return Mod!(T, mod).plain(t % mod);
  }
  static auto fromNegative(T t)
  {
    pragma(inline, true);
    return Mod!(T, mod).plain(t % mod + mod);
  }
  string toString()
  {
    return to!string(_rep);
  }
  debug invariant
  {
    assert(_rep >= 0 && _rep < mod);
  }
  auto opBinary(string op)(This b)
  {
    static if (op == "+")
      {
	T resrep = _rep + b._rep;
	if (resrep >= mod) resrep -= mod;
	return This.plain(resrep);
      }
    else static if (op == "-")
      {
	T resrep = _rep - b._rep;
	if (resrep < 0) resrep += mod;
	return This.plain(resrep);
      }
    else static if (op == "*")
      {
	return This.fromPositive(_rep * b._rep);
      }
  }
  auto opOpAssign(string op)(This b)
  {
    mixin(q{_rep }, text(op, "="), q{b._rep;});
    static if (op == "+")
      {
	if (_rep >= mod) _rep -= mod;
      }
    else static if (op == "-")
      {
	if (_rep < 0) _rep += mod;
      }
    else static if (op == "*")
      {
	_rep %= mod;
      }
    return this;
  }

  auto opBinary(string op)(long exp)
       if (op == "^^")
	 {
	   return pow(exp);
	 }
  mixin powMethod;
}

mixin template powMethod()
{
  auto pow(this ThisType)(long exp)
    {
      auto res = ThisType(1);
      auto pow = ThisType(this);
      while(exp)
	{
	  if (exp & 1) res *= pow;
	  pow *= pow;
	  exp >>= 1;
	}
      return res;
    }
}
// INPUT
char frontChar;
void popChar()
{
  import core.stdc.stdio;
  frontChar = cast(char) getchar;
  if (feof(stdin)) frontChar = ' ';
}
auto makeArray(T, S...)(S s)
{
  enum brackets = () {
    string res;
    foreach(i; 0 .. s.length)
      res ~= "[]";
    return res;
  } ();
  mixin(q{alias Type = T}, brackets, q{;});
  return new Type(s);
}
template isNumberLike(T)
{
  enum isNumberLike = is(typeof((){T test; test = test + test * test; test *= test;}()));
}
void popWhite()
{
  import std.ascii;
  while (frontChar.isWhite) popChar;
}
void print(T...)(T t)
{
  static foreach(ti; t)
    {
      static if (is(typeof(ti) == string))
	{
	  write(ti, " ");
	}
      else static if (isArray!(typeof(ti)))
	{
	  foreach(e; ti) print(e);
	}
      else static if (isTuple!(typeof(ti)))
	{
	  static foreach(i; ti.length) writesp(ti[i]);
	}
      else
	{
	  write(ti, " ");
	}
    }
}
void println(T...)(T t)
{
  static if (t.length == 0)
    writeln;
  static foreach(ti; t)
    {
      print(ti);
      writeln;
    }
}
auto next(alias T, S...)(S s)
{
  pragma(inline, true);
  static if (s.length > 0)
    {
      auto res = makeArray!(typeof(next!T()))(s);
      S t;
      enum loops = () {
	string res;
	foreach(i; 0 .. s.length)
	  {
	    auto ti = text(q{t[}, i, q{]});
	    res ~= text(q{for(}, ti, q{ = 0;}, ti , q{ < s[}, i, q{];}, ti, q{++)});
	  }
	return res;
      } ();
      enum indexing = () {
	string res = "res";
	foreach(i; 0 .. s.length)
	  res ~= text(q{[t[}, i, q{]]});
	return res;
      } ();
      mixin(loops, indexing, q{ = next!T;});
      return res;
    }
  else
    {
      static if (isNumberLike!T)
	{
	  import std.ascii: isDigit;
	  T res;
	  while(frontChar.isWhite) popChar;
	  T multiplier = T(1);
	  if (frontChar == '-')
	    {
	      multiplier = T(-1);
	      popChar;
	    }
	  else if (frontChar == '+')
	    {
	      popChar;
	    }
	  debug assert(frontChar.isDigit);
	  do
	    {
	      res = res * T(10) + T(frontChar - '0');
	      popChar;
	    }
	  while(frontChar.isDigit);
	  return multiplier * res;
	}
      else static if (is(T == string))
	{
	  import std.ascii: isWhite;
	  string res;
	  while(frontChar.isWhite)
	    popChar;
	  while(!frontChar.isWhite)
	    {
	      res ~= frontChar;
	      popChar;
	    }
	  return res;
	}
      else static if (is(T == char))
	{
	  while(frontChar.isWhite) popChar;
	  auto res = frontChar;
	  popChar;
	  return res;
	}
      else
	{
	  return T.read;
	}
    }
}

import std.stdio;
import std.uni;
import std.conv;
import std.container;
import std.functional;
import std.algorithm;
import std.array;
import std.typecons;
import std.range;
import std.numeric;
import std.math;
import std.traits;

void main(string[] args)
{
  auto a00 = next!long;
  auto a01 = next!long;
  auto a10 = next!long;
  auto a11 = next!long;
  auto c0 = cast(long) (round((real(1.0) + sqrt(real(1.0) + 8.0 * a00)) / real(2.0)));
  auto c0p = cast(long) (round((real(1.0) - sqrt(real(1.0) + 8.0 * a00)) / real(2.0)));
  auto c1 = cast(long) (round((real(1.0) + sqrt(real(1.0) + 8.0 * a11)) / real(2.0)));
  auto c1p = cast(long) (round((real(1.0) - sqrt(real(1.0) + 8.0 * a11)) / real(2.0)));
  if (((c0 - 1) * c0) / 2 != a00 || ((c1 - 1) * c1) / 2 != a11)
    return writeln("Impossible");

  string tryValues(long c0, long c1)
  {
    if (c0 < 0 || c1 < 0) return null;
    if (a00 + a01 + a10 + a11 != ((c0 + c1 - 1) * (c0 + c1) / 2))
      return null;
    if (c1 == 0)
      return iota(0, c0).map!(i => "0").join;
    auto mul0 = a01 / c1;
    auto res0 = a01 % c1;
    assert(mul0 <= c0);
    if (res0 == 0)
      {
	return chain(iota(0, mul0).map!(i => "0"),
		     iota(0, c1).map!(i => "1"),
		     iota(0, c0 - mul0).map!(i => "0")).join;
      }
    else
      {
	return chain(iota(0, mul0).map!(i => "0"),
		     iota(0, c1 - res0).map!(i => "1"),
		     only("0"),
		     iota(0, res0).map!(i => "1"),
		     iota(0, c0 - mul0 - 1).map!(i => "0")).join;
      }
  }

  foreach(tc0; [c0, c0p])
    foreach(tc1; [c1, c1p])
      if (string res = tryValues(tc0, tc1))
	return res.writeln;
  writeln("Impossible");

}

static this()
{
  popChar;
}

struct Any(T)
{
  static auto read()
  {
    static if (is(T == char))
      {
	auto res = frontChar;
	popChar;
	return res;
      }
  }
}

struct Unsigned(T)
{
  auto read()
  {
    auto res = T(0);
    while(!frontChar.isDigit) popChar;
    do
      {
	res = res * T(10) + T(frontChar - '0');
	popChar;
      }
    while(frontChar.isDigit);
    return res;
  }
}

struct Matrix(T, alias rows, alias cols)
{
  T[][] _cell;
  alias _cell this;
  // Ring constructor
  this(int n)
  {
    final switch(n)
      {
      case 0:
	_cell = new T[][](rows, cols);
	break;
      case 1:
	debug assert(rows == cols);
	_cell = new T[][](rows, cols);
	foreach(i; 0 .. rows)
	  _cell[i][i] = T(1);
	break;
      }
  }
  // Copy constructor
  this(Matrix!(T, rows, cols) other)
  {
    _cell = new T[][](rows, cols);
    foreach(i; 0 .. rows)
      _cell[i][] = other[i][];
  }
  auto opBinary(string op)(Matrix!(T, rows, cols) other)
       if (op == "+" || op == "-")
	 {
	   auto res = Matrix!(T, rows, cols)(0);
	   foreach(i; 0 .. rows)
	     foreach(j; 0 .. cols)
	       res[i][j] = mixin(q{this[i][j]}, op, q{other[i][j]});
	   return res;
	 }
  auto opBinary(string op, alias otherRows, alias otherCols)(Matrix!(T, otherRows, otherCols) other)
       if (op == "*")
	 {
	   debug assert(cols == otherRows);
	   auto res = Matrix!(T, rows, otherCols)(0);
	   foreach(i; 0 .. rows)
	     foreach(k; 0 .. cols)
	       foreach(j; 0 .. otherCols)
		 res[i][j] += this[i][k] * other[k][j];
	   return res;
	 }
  ref auto opOpAssign(string op)(Matrix!(T, rows, cols) other)
  {
    static if (op == "*")
      {
	debug assert(rows == cols);
	alias n = rows;
	auto res = Matrix!(T, n, n)(0);
	T factor;
	foreach(i; 0 .. n)
	  foreach(k; 0 .. n)
	    foreach(j; 0 .. n)
	      res[i][j] += this[i][k] * other[k][j];
	foreach(i; 0 .. n)
	  this[i][] = res[i][];
      }
    else
      {
	foreach(i; 0 .. rows)
	  foreach(j; 0 .. cols)
	    mixin(q{this[i][j] }, text(op, q{=}), q{ other[i][j];});
      }
    return this;
  }
  mixin powMethod;
}

struct Mod(T, alias mod)
{
  alias This = Mod!(T, mod);
  T _rep;
  this(T t)
  {
    _rep = t % mod;
    if (_rep < 0) _rep += mod;
  }
  static auto plain(T t)
  {
    auto res = Mod!(T, mod)();
    res._rep = t;
    return res;
  }
  static auto fromPositive(T t)
  {
    pragma(inline, true);
    return Mod!(T, mod).plain(t % mod);
  }
  static auto fromNegative(T t)
  {
    pragma(inline, true);
    return Mod!(T, mod).plain(t % mod + mod);
  }
  string toString()
  {
    return to!string(_rep);
  }
  debug invariant
  {
    assert(_rep >= 0 && _rep < mod);
  }
  auto opBinary(string op)(This b)
  {
    static if (op == "+")
      {
	T resrep = _rep + b._rep;
	if (resrep >= mod) resrep -= mod;
	return This.plain(resrep);
      }
    else static if (op == "-")
      {
	T resrep = _rep - b._rep;
	if (resrep < 0) resrep += mod;
	return This.plain(resrep);
      }
    else static if (op == "*")
      {
	return This.fromPositive(_rep * b._rep);
      }
  }
  auto opOpAssign(string op)(This b)
  {
    mixin(q{_rep }, text(op, "="), q{b._rep;});
    static if (op == "+")
      {
	if (_rep >= mod) _rep -= mod;
      }
    else static if (op == "-")
      {
	if (_rep < 0) _rep += mod;
      }
    else static if (op == "*")
      {
	_rep %= mod;
      }
    return this;
  }

  auto opBinary(string op)(long exp)
       if (op == "^^")
	 {
	   return pow(exp);
	 }
  mixin powMethod;
}

mixin template powMethod()
{
  auto pow(this ThisType)(long exp)
    {
      auto res = ThisType(1);
      auto pow = ThisType(this);
      while(exp)
	{
	  if (exp & 1) res *= pow;
	  pow *= pow;
	  exp >>= 1;
	}
      return res;
    }
}
// INPUT
char frontChar;
void popChar()
{
  import core.stdc.stdio;
  frontChar = cast(char) getchar;
  if (feof(stdin)) frontChar = ' ';
}
auto makeArray(T, S...)(S s)
{
  enum brackets = () {
    string res;
    foreach(i; 0 .. s.length)
      res ~= "[]";
    return res;
  } ();
  mixin(q{alias Type = T}, brackets, q{;});
  return new Type(s);
}
template isNumberLike(T)
{
  enum isNumberLike = is(typeof((){T test; test = test + test * test; test *= test;}()));
}
void popWhite()
{
  import std.ascii;
  while (frontChar.isWhite) popChar;
}
void print(T...)(T t)
{
  static foreach(ti; t)
    {
      static if (is(typeof(ti) == string))
	{
	  write(ti, " ");
	}
      else static if (isArray!(typeof(ti)))
	{
	  foreach(e; ti) print(e);
	}
      else static if (isTuple!(typeof(ti)))
	{
	  static foreach(i; ti.length) writesp(ti[i]);
	}
      else
	{
	  write(ti, " ");
	}
    }
}
void println(T...)(T t)
{
  static if (t.length == 0)
    writeln;
  static foreach(ti; t)
    {
      print(ti);
      writeln;
    }
}
auto next(alias T, S...)(S s)
{
  pragma(inline, true);
  static if (s.length > 0)
    {
      auto res = makeArray!(typeof(next!T()))(s);
      S t;
      enum loops = () {
	string res;
	foreach(i; 0 .. s.length)
	  {
	    auto ti = text(q{t[}, i, q{]});
	    res ~= text(q{for(}, ti, q{ = 0;}, ti , q{ < s[}, i, q{];}, ti, q{++)});
	  }
	return res;
      } ();
      enum indexing = () {
	string res = "res";
	foreach(i; 0 .. s.length)
	  res ~= text(q{[t[}, i, q{]]});
	return res;
      } ();
      mixin(loops, indexing, q{ = next!T;});
      return res;
    }
  else
    {
      static if (isNumberLike!T)
	{
	  import std.ascii: isDigit;
	  T res;
	  while(frontChar.isWhite) popChar;
	  T multiplier = T(1);
	  if (frontChar == '-')
	    {
	      multiplier = T(-1);
	      popChar;
	    }
	  else if (frontChar == '+')
	    {
	      popChar;
	    }
	  debug assert(frontChar.isDigit);
	  do
	    {
	      res = res * T(10) + T(frontChar - '0');
	      popChar;
	    }
	  while(frontChar.isDigit);
	  return multiplier * res;
	}
      else static if (is(T == string))
	{
	  import std.ascii: isWhite;
	  string res;
	  while(frontChar.isWhite)
	    popChar;
	  while(!frontChar.isWhite)
	    {
	      res ~= frontChar;
	      popChar;
	    }
	  return res;
	}
      else static if (is(T == char))
	{
	  while(frontChar.isWhite) popChar;
	  auto res = frontChar;
	  popChar;
	  return res;
	}
      else
	{
	  return T.read;
	}
    }
}

import std.stdio;
import std.uni;
import std.conv;
import std.container;
import std.functional;
import std.algorithm;
import std.array;
import std.typecons;
import std.range;
import std.numeric;
import std.math;
import std.traits;

void main(string[] args)
{
  auto a00 = next!long;
  auto a01 = next!long;
  auto a10 = next!long;
  auto a11 = next!long;
  auto c0 = cast(long) (round((real(1.0) + sqrt(real(1.0) + 8.0 * a00)) / real(2.0)));
  auto c1 = cast(long) (round((real(1.0) + sqrt(real(1.0) + 8.0 * a11)) / real(2.0)));
  if (((c0 - 1) * c0) / 2 != a00 || ((c1 - 1) * c1) / 2 != a11
      || a00 + a01 + a10 + a11 != ((c0 + c1 - 1) * (c0 + c1)) / 2)
    return writeln("Impossible");
  auto mul0 = a01 / c1;
  auto res0 = a01 % c1;
  assert(mul0 <= c0);
  if (res0 == 0)
    {
      chain(iota(0, mul0).map!(i => "0"),
	    iota(0, c1).map!(i => "1"),
	    iota(0, c0 - mul0).map!(i => "0")).join.writeln;
    }
  else
    {
      chain(iota(0, mul0).map!(i => "0"),
	    iota(0, c1 - res0).map!(i => "1"),
	    only("0"),
	    iota(0, res0).map!(i => "1"),
	    iota(0, c0 - mul0 - 1).map!(i => "0")).join.writeln;
    }
}

static this()
{
  popChar;
}

struct Any(T)
{
  static auto read()
  {
    static if (is(T == char))
      {
	auto res = frontChar;
	popChar;
	return res;
      }
  }
}

struct Unsigned(T)
{
  auto read()
  {
    auto res = T(0);
    while(!frontChar.isDigit) popChar;
    do
      {
	res = res * T(10) + T(frontChar - '0');
	popChar;
      }
    while(frontChar.isDigit);
    return res;
  }
}

struct Matrix(T, alias rows, alias cols)
{
  T[][] _cell;
  alias _cell this;
  // Ring constructor
  this(int n)
  {
    final switch(n)
      {
      case 0:
	_cell = new T[][](rows, cols);
	break;
      case 1:
	debug assert(rows == cols);
	_cell = new T[][](rows, cols);
	foreach(i; 0 .. rows)
	  _cell[i][i] = T(1);
	break;
      }
  }
  // Copy constructor
  this(Matrix!(T, rows, cols) other)
  {
    _cell = new T[][](rows, cols);
    foreach(i; 0 .. rows)
      _cell[i][] = other[i][];
  }
  auto opBinary(string op)(Matrix!(T, rows, cols) other)
       if (op == "+" || op == "-")
	 {
	   auto res = Matrix!(T, rows, cols)(0);
	   foreach(i; 0 .. rows)
	     foreach(j; 0 .. cols)
	       res[i][j] = mixin(q{this[i][j]}, op, q{other[i][j]});
	   return res;
	 }
  auto opBinary(string op, alias otherRows, alias otherCols)(Matrix!(T, otherRows, otherCols) other)
       if (op == "*")
	 {
	   debug assert(cols == otherRows);
	   auto res = Matrix!(T, rows, otherCols)(0);
	   foreach(i; 0 .. rows)
	     foreach(k; 0 .. cols)
	       foreach(j; 0 .. otherCols)
		 res[i][j] += this[i][k] * other[k][j];
	   return res;
	 }
  ref auto opOpAssign(string op)(Matrix!(T, rows, cols) other)
  {
    static if (op == "*")
      {
	debug assert(rows == cols);
	alias n = rows;
	auto res = Matrix!(T, n, n)(0);
	T factor;
	foreach(i; 0 .. n)
	  foreach(k; 0 .. n)
	    foreach(j; 0 .. n)
	      res[i][j] += this[i][k] * other[k][j];
	foreach(i; 0 .. n)
	  this[i][] = res[i][];
      }
    else
      {
	foreach(i; 0 .. rows)
	  foreach(j; 0 .. cols)
	    mixin(q{this[i][j] }, text(op, q{=}), q{ other[i][j];});
      }
    return this;
  }
  mixin powMethod;
}

struct Mod(T, alias mod)
{
  alias This = Mod!(T, mod);
  T _rep;
  this(T t)
  {
    _rep = t % mod;
    if (_rep < 0) _rep += mod;
  }
  static auto plain(T t)
  {
    auto res = Mod!(T, mod)();
    res._rep = t;
    return res;
  }
  static auto fromPositive(T t)
  {
    pragma(inline, true);
    return Mod!(T, mod).plain(t % mod);
  }
  static auto fromNegative(T t)
  {
    pragma(inline, true);
    return Mod!(T, mod).plain(t % mod + mod);
  }
  string toString()
  {
    return to!string(_rep);
  }
  debug invariant
  {
    assert(_rep >= 0 && _rep < mod);
  }
  auto opBinary(string op)(This b)
  {
    static if (op == "+")
      {
	T resrep = _rep + b._rep;
	if (resrep >= mod) resrep -= mod;
	return This.plain(resrep);
      }
    else static if (op == "-")
      {
	T resrep = _rep - b._rep;
	if (resrep < 0) resrep += mod;
	return This.plain(resrep);
      }
    else static if (op == "*")
      {
	return This.fromPositive(_rep * b._rep);
      }
  }
  auto opOpAssign(string op)(This b)
  {
    mixin(q{_rep }, text(op, "="), q{b._rep;});
    static if (op == "+")
      {
	if (_rep >= mod) _rep -= mod;
      }
    else static if (op == "-")
      {
	if (_rep < 0) _rep += mod;
      }
    else static if (op == "*")
      {
	_rep %= mod;
      }
    return this;
  }

  auto opBinary(string op)(long exp)
       if (op == "^^")
	 {
	   return pow(exp);
	 }
  mixin powMethod;
}

mixin template powMethod()
{
  auto pow(this ThisType)(long exp)
    {
      auto res = ThisType(1);
      auto pow = ThisType(this);
      while(exp)
	{
	  if (exp & 1) res *= pow;
	  pow *= pow;
	  exp >>= 1;
	}
      return res;
    }
}
// INPUT
char frontChar;
void popChar()
{
  import core.stdc.stdio;
  frontChar = cast(char) getchar;
  if (feof(stdin)) frontChar = ' ';
}
auto makeArray(T, S...)(S s)
{
  enum brackets = () {
    string res;
    foreach(i; 0 .. s.length)
      res ~= "[]";
    return res;
  } ();
  mixin(q{alias Type = T}, brackets, q{;});
  return new Type(s);
}
template isNumberLike(T)
{
  enum isNumberLike = is(typeof((){T test; test = test + test * test; test *= test;}()));
}
void popWhite()
{
  import std.ascii;
  while (frontChar.isWhite) popChar;
}
void print(T...)(T t)
{
  static foreach(ti; t)
    {
      static if (is(typeof(ti) == string))
	{
	  write(ti, " ");
	}
      else static if (isArray!(typeof(ti)))
	{
	  foreach(e; ti) print(e);
	}
      else static if (isTuple!(typeof(ti)))
	{
	  static foreach(i; ti.length) writesp(ti[i]);
	}
      else
	{
	  write(ti, " ");
	}
    }
}
void println(T...)(T t)
{
  static if (t.length == 0)
    writeln;
  static foreach(ti; t)
    {
      print(ti);
      writeln;
    }
}
auto next(alias T, S...)(S s)
{
  pragma(inline, true);
  static if (s.length > 0)
    {
      auto res = makeArray!(typeof(next!T()))(s);
      S t;
      enum loops = () {
	string res;
	foreach(i; 0 .. s.length)
	  {
	    auto ti = text(q{t[}, i, q{]});
	    res ~= text(q{for(}, ti, q{ = 0;}, ti , q{ < s[}, i, q{];}, ti, q{++)});
	  }
	return res;
      } ();
      enum indexing = () {
	string res = "res";
	foreach(i; 0 .. s.length)
	  res ~= text(q{[t[}, i, q{]]});
	return res;
      } ();
      mixin(loops, indexing, q{ = next!T;});
      return res;
    }
  else
    {
      static if (isNumberLike!T)
	{
	  import std.ascii: isDigit;
	  T res;
	  while(frontChar.isWhite) popChar;
	  T multiplier = T(1);
	  if (frontChar == '-')
	    {
	      multiplier = T(-1);
	      popChar;
	    }
	  else if (frontChar == '+')
	    {
	      popChar;
	    }
	  debug assert(frontChar.isDigit);
	  do
	    {
	      res = res * T(10) + T(frontChar - '0');
	      popChar;
	    }
	  while(frontChar.isDigit);
	  return multiplier * res;
	}
      else static if (is(T == string))
	{
	  import std.ascii: isWhite;
	  string res;
	  while(frontChar.isWhite)
	    popChar;
	  while(!frontChar.isWhite)
	    {
	      res ~= frontChar;
	      popChar;
	    }
	  return res;
	}
      else static if (is(T == char))
	{
	  while(frontChar.isWhite) popChar;
	  auto res = frontChar;
	  popChar;
	  return res;
	}
      else
	{
	  return T.read;
	}
    }
}