#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    static int inv_mod(int a, int m = MOD) {
        // https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Example
        int g = m, r = a, x = 0, y = 1;

        while (r != 0) {
            int q = g / r;
            g %= r; swap(g, r);
            x -= q * y; swap(x, y);
        }

        return x < 0 ? x + m : x;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    _m_int inv() const {
        return inv_mod(val);
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

extern const int CHECK_MOD = 2014437851;
using check_int = _m_int<CHECK_MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse) {
            if (is_arithmetic<T>::value) {
                for (int i = 0; i < n; i++)
                    values[i] /= n;
            } else {
                T inv = T(1) / n;

                for (int i = 0; i < n; i++)
                    values[i] *= inv;
            }
        }
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int N = round_up_power_two(max(n, m));
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
#ifndef NEAL_DEBUG
    cin.tie(nullptr);
#endif

    cin >> N >> M;
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, ways);

    vector<check_int> check_ways(1 << BITS, 0);
    check_ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, check_ways);

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);
        vector<check_int> check_freq(1 << BITS, 0);

        for (int weight : cycle) {
            freq[weight]++;
            check_freq[weight]++;
        }

        walsh_hadamard::fwht_xor(1 << BITS, freq);
        walsh_hadamard::fwht_xor(1 << BITS, check_freq);

        for (int i = 0; i < 1 << BITS; i++) {
            ways[i] *= freq[i];
            check_ways[i] *= check_freq[i];
        }
    }

    walsh_hadamard::fwht_xor(1 << BITS, ways, true);
    walsh_hadamard::fwht_xor(1 << BITS, check_ways, true);

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if ((ways[option] != 0 || check_ways[option] != 0) && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

namespace IO {
    const int BUFFER_SIZE = 1 << 15;

    char input_buffer[BUFFER_SIZE];
    int input_pos = 0, input_len = 0;

    void _update_input_buffer() {
        input_len = fread(input_buffer, sizeof(char), BUFFER_SIZE, stdin);
        input_pos = 0;

        if (input_len == 0)
            input_buffer[0] = EOF;
    }

    inline char next_char(bool advance = true) {
        if (input_pos >= input_len)
            _update_input_buffer();

        return input_buffer[advance ? input_pos++ : input_pos];
    }

    template<typename T>
    inline void read_int(T &number) {
        bool negative = false;
        number = 0;

        while (!isdigit(next_char(false)))
            if (next_char() == '-')
                negative = true;

        do {
            number = 10 * number + (next_char() - '0');
        } while (isdigit(next_char(false)));

        if (negative)
            number = -number;
    }

    template<typename T, typename... Args>
    inline void read_int(T &number, Args &... args) {
        read_int(number);
        read_int(args...);
    }
}

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    static int inv_mod(int a, int m = MOD) {
        // https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Example
        int g = m, r = a, x = 0, y = 1;

        while (r != 0) {
            int q = g / r;
            g %= r; swap(g, r);
            x -= q * y; swap(x, y);
        }

        return x < 0 ? x + m : x;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    _m_int inv() const {
        return inv_mod(val);
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

extern const int CHECK_MOD = 2014437851;
using check_int = _m_int<CHECK_MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    typename enable_if<is_integral<T>::value>::type divide_all(int n, vector<T> &values) {
        for (int i = 0; i < n; i++)
            values[i] >>= __builtin_ctz(n);
    }

    template<typename T>
    typename enable_if<!is_integral<T>::value>::type divide_all(int n, vector<T> &values) {
        T inv_n = T(1) / n;

        for (int i = 0; i < n; i++)
            values[i] *= inv_n;
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse)
            divide_all(n, values);
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int N = round_up_power_two(max(n, m));
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    IO::read_int(N, M);
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        IO::read_int(u, v, weight);
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, ways);

    vector<check_int> check_ways(1 << BITS, 0);
    check_ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, check_ways);

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);
        vector<check_int> check_freq(1 << BITS, 0);

        for (int weight : cycle) {
            freq[weight]++;
            check_freq[weight]++;
        }

        walsh_hadamard::fwht_xor(1 << BITS, freq);
        walsh_hadamard::fwht_xor(1 << BITS, check_freq);

        for (int i = 0; i < 1 << BITS; i++) {
            ways[i] *= freq[i];
            check_ways[i] *= check_freq[i];
        }
    }

    walsh_hadamard::fwht_xor(1 << BITS, ways, true);
    walsh_hadamard::fwht_xor(1 << BITS, check_ways, true);

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if ((ways[option] != 0 || check_ways[option] != 0) && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    static int inv_mod(int a, int m = MOD) {
        // https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Example
        int g = m, r = a, x = 0, y = 1;

        while (r != 0) {
            int q = g / r;
            g %= r; swap(g, r);
            x -= q * y; swap(x, y);
        }

        return x < 0 ? x + m : x;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    _m_int inv() const {
        return inv_mod(val);
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse) {
            for (int i = 0; i < n; i++)
                values[i] /= n;
        }
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right, bool circular = false) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int output_size = circular ? round_up_power_two(max(n, m)) : n + m - 1;
        int N = round_up_power_two(output_size);
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        left.resize(output_size);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
#ifndef NEAL_DEBUG
    cin.tie(nullptr);
#endif

    cin >> N >> M;
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);

        for (int weight : cycle)
            freq[weight]++;

        ways = walsh_hadamard::xor_convolution<mod_int>(ways, freq, true);
    }

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if (ways[option] != 0 && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    static int inv_mod(int a, int m = MOD) {
        // https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Example
        int g = m, r = a, x = 0, y = 1;

        while (r != 0) {
            int q = g / r;
            g %= r; swap(g, r);
            x -= q * y; swap(x, y);
        }

        return x < 0 ? x + m : x;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    _m_int inv() const {
        return inv_mod(val);
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse) {
            for (int i = 0; i < n; i++)
                values[i] /= n;
        }
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right, bool circular = false) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int output_size = circular ? round_up_power_two(max(n, m)) : n + m - 1;
        int N = round_up_power_two(output_size);
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        left.resize(output_size);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
#ifndef NEAL_DEBUG
    cin.tie(nullptr);
#endif

    cin >> N >> M;
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);

        for (int weight : cycle)
            freq[weight]++;

        ways = walsh_hadamard::xor_convolution<mod_int>(ways, freq);
    }

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if (ways[option] != 0 && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    static int inv_mod(int a, int m = MOD) {
        // https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Example
        int g = m, r = a, x = 0, y = 1;

        while (r != 0) {
            int q = g / r;
            g %= r; swap(g, r);
            x -= q * y; swap(x, y);
        }

        return x < 0 ? x + m : x;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    _m_int inv() const {
        return inv_mod(val);
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

extern const int CHECK_MOD = 2014437851;
using check_int = _m_int<CHECK_MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    typename enable_if<is_integral<T>::value, void>::type divide_all(int n, vector<T> &values) {
        assert(is_power_of_two(n));

        for (int i = 0; i < n; i++)
            values[i] >>= __builtin_ctz(n);
    }

    template<typename T>
    typename enable_if<!is_integral<T>::value, void>::type divide_all(int n, vector<T> &values) {
        T inv_n = T(1) / n;

        for (int i = 0; i < n; i++)
            values[i] *= inv_n;
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse)
            divide_all(n, values);
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int N = round_up_power_two(max(n, m));
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
#ifndef NEAL_DEBUG
    cin.tie(nullptr);
#endif

    cin >> N >> M;
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, ways);

    vector<check_int> check_ways(1 << BITS, 0);
    check_ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, check_ways);

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);
        vector<check_int> check_freq(1 << BITS, 0);

        for (int weight : cycle) {
            freq[weight]++;
            check_freq[weight]++;
        }

        walsh_hadamard::fwht_xor(1 << BITS, freq);
        walsh_hadamard::fwht_xor(1 << BITS, check_freq);

        for (int i = 0; i < 1 << BITS; i++) {
            ways[i] *= freq[i];
            check_ways[i] *= check_freq[i];
        }
    }

    walsh_hadamard::fwht_xor(1 << BITS, ways, true);
    walsh_hadamard::fwht_xor(1 << BITS, check_ways, true);

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if ((ways[option] != 0 || check_ways[option] != 0) && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    static const int SAVE_INV = 1e6 + 5;
    static _m_int save_inv[SAVE_INV];

    static void prepare_save() {
        // Make sure MOD is prime, which is necessary for the inverse algorithm below.
        for (int p = 2; p * p <= MOD; p += p % 2 + 1)
            assert(MOD % p != 0);

        save_inv[0] = 0;
        save_inv[1] = 1;

        for (int i = 2; i < SAVE_INV; i++)
            save_inv[i] = save_inv[MOD % i] * (MOD - MOD / i);
    }

    _m_int inv() const {
        if (save_inv[1] == 0)
            prepare_save();

        if (val < SAVE_INV)
            return save_inv[val];

        _m_int product = 1;
        int v = val;

        while (v >= SAVE_INV) {
            product *= MOD - MOD / v;
            v = MOD % v;
        }

        return product * save_inv[v];
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

template<const int &MOD> _m_int<MOD> _m_int<MOD>::save_inv[_m_int<MOD>::SAVE_INV];

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

extern const int CHECK_MOD = 2014437851;
using check_int = _m_int<CHECK_MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse) {
            for (int i = 0; i < n; i++)
                values[i] /= n;
        }
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int N = round_up_power_two(max(n, m));
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
#ifndef NEAL_DEBUG
    cin.tie(nullptr);
#endif

    cin >> N >> M;
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;

    vector<check_int> check_ways(1 << BITS, 0);
    check_ways[0] = 1;

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);
        vector<check_int> check_freq(1 << BITS, 0);

        for (int weight : cycle) {
            freq[weight]++;
            check_freq[weight]++;
        }

        ways = walsh_hadamard::xor_convolution<mod_int>(ways, freq);
        check_ways = walsh_hadamard::xor_convolution<check_int>(check_ways, check_freq);
    }

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if ((ways[option] != 0 || check_ways[option] != 0) && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

namespace IO {
    const int BUFFER_SIZE = 1 << 15;

    char input_buffer[BUFFER_SIZE];
    int input_pos = 0, input_len = 0;

    void _update_input_buffer() {
        input_len = fread(input_buffer, sizeof(char), BUFFER_SIZE, stdin);
        input_pos = 0;

        if (input_len == 0)
            input_buffer[0] = EOF;
    }

    inline char next_char(bool advance = true) {
        if (input_pos >= input_len)
            _update_input_buffer();

        return input_buffer[advance ? input_pos++ : input_pos];
    }

    template<typename T>
    inline void read_int(T &number) {
        bool negative = false;
        number = 0;

        while (!isdigit(next_char(false)))
            if (next_char() == '-')
                negative = true;

        do {
            number = 10 * number + (next_char() - '0');
        } while (isdigit(next_char(false)));

        if (negative)
            number = -number;
    }

    template<typename T, typename... Args>
    inline void read_int(T &number, Args &... args) {
        read_int(number);
        read_int(args...);
    }
}

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    static int inv_mod(int a, int m = MOD) {
        // https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Example
        int g = m, r = a, x = 0, y = 1;

        while (r != 0) {
            int q = g / r;
            g %= r; swap(g, r);
            x -= q * y; swap(x, y);
        }

        return x < 0 ? x + m : x;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    _m_int inv() const {
        return inv_mod(val);
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

extern const int CHECK_MOD = 2014437851;
using check_int = _m_int<CHECK_MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    typename enable_if<is_integral<T>::value, void>::type divide_all(int n, vector<T> &values) {
        assert(is_power_of_two(n));

        for (int i = 0; i < n; i++)
            values[i] >>= __builtin_ctz(n);
    }

    template<typename T>
    typename enable_if<!is_integral<T>::value, void>::type divide_all(int n, vector<T> &values) {
        T inv_n = T(1) / n;

        for (int i = 0; i < n; i++)
            values[i] *= inv_n;
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse)
            divide_all(n, values);
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int N = round_up_power_two(max(n, m));
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    IO::read_int(N, M);
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        IO::read_int(u, v, weight);
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, ways);

    vector<check_int> check_ways(1 << BITS, 0);
    check_ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, check_ways);

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);
        vector<check_int> check_freq(1 << BITS, 0);

        for (int weight : cycle) {
            freq[weight]++;
            check_freq[weight]++;
        }

        walsh_hadamard::fwht_xor(1 << BITS, freq);
        walsh_hadamard::fwht_xor(1 << BITS, check_freq);

        for (int i = 0; i < 1 << BITS; i++) {
            ways[i] *= freq[i];
            check_ways[i] *= check_freq[i];
        }
    }

    walsh_hadamard::fwht_xor(1 << BITS, ways, true);
    walsh_hadamard::fwht_xor(1 << BITS, check_ways, true);

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if ((ways[option] != 0 || check_ways[option] != 0) && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    static const int SAVE_INV = 1e6 + 5;
    static _m_int save_inv[SAVE_INV];

    static void prepare_save() {
        // Make sure MOD is prime, which is necessary for the inverse algorithm below.
        for (int p = 2; p * p <= MOD; p += p % 2 + 1)
            assert(MOD % p != 0);

        save_inv[0] = 0;
        save_inv[1] = 1;

        for (int i = 2; i < SAVE_INV; i++)
            save_inv[i] = save_inv[MOD % i] * (MOD - MOD / i);
    }

    _m_int inv() const {
        if (save_inv[1] == 0)
            prepare_save();

        if (val < SAVE_INV)
            return save_inv[val];

        _m_int product = 1;
        int v = val;

        while (v >= SAVE_INV) {
            product *= MOD - MOD / v;
            v = MOD % v;
        }

        return product * save_inv[v];
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

template<const int &MOD> _m_int<MOD> _m_int<MOD>::save_inv[_m_int<MOD>::SAVE_INV];

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

extern const int CHECK_MOD = 2014437851;
using check_int = _m_int<CHECK_MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse) {
            for (int i = 0; i < n; i++)
                values[i] /= n;
        }
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right, bool circular = false) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int output_size = circular ? round_up_power_two(max(n, m)) : n + m - 1;
        int N = round_up_power_two(output_size);
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        left.resize(output_size);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
#ifndef NEAL_DEBUG
    cin.tie(nullptr);
#endif

    cin >> N >> M;
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;

    vector<check_int> check_ways(1 << BITS, 0);
    check_ways[0] = 1;

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);
        vector<check_int> check_freq(1 << BITS, 0);

        for (int weight : cycle) {
            freq[weight]++;
            check_freq[weight]++;
        }

        ways = walsh_hadamard::xor_convolution<mod_int>(ways, freq);
        check_ways = walsh_hadamard::xor_convolution<check_int>(check_ways, check_freq);
    }

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if ((ways[option] != 0 || check_ways[option] != 0) && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

template<typename T> ostream& operator<<(ostream &os, const vector<T> &v) { os << '{'; string sep; for (const auto &x : v) os << sep << x, sep = ", "; return os << '}'; }
template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << ", " << p.second << ')'; }

void dbg_out() { cerr << endl; }
template<typename Head, typename... Tail> void dbg_out(Head H, Tail... T) { cerr << ' ' << H; dbg_out(T...); }

#ifdef NEAL_DEBUG
#define dbg(...) cerr << "(" << #__VA_ARGS__ << "):", dbg_out(__VA_ARGS__)
#else
#define dbg(...)
#endif

template<const int &MOD>
struct _m_int {
    int val;

    _m_int(int64_t v = 0) {
        if (v < 0) v = v % MOD + MOD;
        if (v >= MOD) v %= MOD;
        val = v;
    }

    static int inv_mod(int a, int m = MOD) {
        // https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Example
        int g = m, r = a, x = 0, y = 1;

        while (r != 0) {
            int q = g / r;
            g %= r; swap(g, r);
            x -= q * y; swap(x, y);
        }

        return x < 0 ? x + m : x;
    }

    explicit operator int() const { return val; }
    explicit operator int64_t() const { return val; }

    _m_int& operator+=(const _m_int &other) {
        val -= MOD - other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    _m_int& operator-=(const _m_int &other) {
        val -= other.val;
        if (val < 0) val += MOD;
        return *this;
    }

    static unsigned fast_mod(uint64_t x, unsigned m = MOD) {
#if !defined(_WIN32) || defined(_WIN64)
        return x % m;
#endif
        // Optimized mod for Codeforces 32-bit machines.
        // x must be less than 2^32 * m for this to work, so that x / m fits in a 32-bit integer.
        unsigned x_high = x >> 32, x_low = (unsigned) x;
        unsigned quot, rem;
        asm("divl %4\n"
            : "=a" (quot), "=d" (rem)
            : "d" (x_high), "a" (x_low), "r" (m));
        return rem;
    }

    _m_int& operator*=(const _m_int &other) {
        val = fast_mod((uint64_t) val * other.val);
        return *this;
    }

    _m_int& operator/=(const _m_int &other) {
        return *this *= other.inv();
    }

    friend _m_int operator+(const _m_int &a, const _m_int &b) { return _m_int(a) += b; }
    friend _m_int operator-(const _m_int &a, const _m_int &b) { return _m_int(a) -= b; }
    friend _m_int operator*(const _m_int &a, const _m_int &b) { return _m_int(a) *= b; }
    friend _m_int operator/(const _m_int &a, const _m_int &b) { return _m_int(a) /= b; }

    _m_int& operator++() {
        val = val == MOD - 1 ? 0 : val + 1;
        return *this;
    }

    _m_int& operator--() {
        val = val == 0 ? MOD - 1 : val - 1;
        return *this;
    }

    _m_int operator++(int) { _m_int before = *this; ++*this; return before; }
    _m_int operator--(int) { _m_int before = *this; --*this; return before; }

    _m_int operator-() const {
        return val == 0 ? 0 : MOD - val;
    }

    bool operator==(const _m_int &other) const { return val == other.val; }
    bool operator!=(const _m_int &other) const { return val != other.val; }

    _m_int inv() const {
        return inv_mod(val);
    }

    _m_int pow(int64_t p) const {
        if (p < 0)
            return inv().pow(-p);

        _m_int a = *this, result = 1;

        while (p > 0) {
            if (p & 1)
                result *= a;

            a *= a;
            p >>= 1;
        }

        return result;
    }

    friend ostream& operator<<(ostream &os, const _m_int &m) {
        return os << m.val;
    }
};

extern const int MOD = 1e9 + 7;
using mod_int = _m_int<MOD>;

extern const int CHECK_MOD = 2014437851;
using check_int = _m_int<CHECK_MOD>;

// https://csacademy.com/blog/fast-fourier-transform-and-variations-of-it
namespace walsh_hadamard {
    bool is_power_of_two(int n) {
        return (n & (n - 1)) == 0;
    }

    int round_up_power_two(int n) {
        while (n & (n - 1))
            n = (n | (n - 1)) + 1;

        return max(n, 1);
    }

    template<typename T>
    typename enable_if<is_integral<T>::value>::type divide_all(int n, vector<T> &values) {
        for (int i = 0; i < n; i++)
            values[i] >>= __builtin_ctz(n);
    }

    template<typename T>
    typename enable_if<!is_integral<T>::value>::type divide_all(int n, vector<T> &values) {
        T inv_n = T(1) / n;

        for (int i = 0; i < n; i++)
            values[i] *= inv_n;
    }

    template<typename T>
    void fwht_xor(int n, vector<T> &values, bool inverse = false) {
        assert(is_power_of_two(n));
        assert((int) values.size() >= n);

        for (int len = 1; len < n; len *= 2)
            for (int start = 0; start < n; start += 2 * len)
                for (int i = start; i < start + len; i++) {
                    T u = values[i];
                    const T &v = values[i + len];
                    values[i] = u + v;
                    values[i + len] = u - v;
                }

        if (inverse)
            divide_all(n, values);
    }

    // Warning: make sure to select the type `T_out` so that it can contain N^2 * max(left) * max(right).
    // One option is to use doubles and round at the end, since we are simply dividing by a power of two to finish.
    template<typename T_out, typename T_in>
    vector<T_out> xor_convolution(const vector<T_in> &_left, const vector<T_in> &_right) {
        if (_left.empty() || _right.empty())
            return {};

        vector<T_out> left(_left.begin(), _left.end());
        vector<T_out> right(_right.begin(), _right.end());

        int n = left.size();
        int m = right.size();

        int N = round_up_power_two(max(n, m));
        left.resize(N, 0);
        right.resize(N, 0);

        if (left == right) {
            fwht_xor(N, left);
            right = left;
        } else {
            fwht_xor(N, left);
            fwht_xor(N, right);
        }

        for (int i = 0; i < N; i++)
            left[i] *= right[i];

        fwht_xor(N, left, true);
        return left;
    }
}

const int BITS = 17;

struct edge {
    int node, weight;

    edge() {}

    edge(int _node, int _weight) : node(_node), weight(_weight) {}
};

int N, M;
vector<vector<edge>> adj;
vector<bool> visited;
vector<int> parent;
vector<int> depth;
vector<int> up_weight;
vector<vector<int>> cycles;

void dfs(int node, int par, int weight) {
    assert(!visited[node]);
    visited[node] = true;
    parent[node] = par;
    depth[node] = par < 0 ? 0 : depth[par] + 1;
    up_weight[node] = weight;

    for (edge &e : adj[node]) {
        if (e.node == par)
            continue;

        if (!visited[e.node]) {
            dfs(e.node, node, e.weight);
        } else if (depth[e.node] < depth[node]) {
            vector<int> cycle = {e.weight};
            int x = node;

            while (x != e.node) {
                cycle.push_back(up_weight[x]);
                x = parent[x];
            }

            cycles.push_back(cycle);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
#ifndef NEAL_DEBUG
    cin.tie(nullptr);
#endif

    cin >> N >> M;
    adj.assign(N, {});
    int all_xor = 0;

    for (int i = 0; i < M; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        u--; v--;
        adj[u].emplace_back(v, weight);
        adj[v].emplace_back(u, weight);
        all_xor ^= weight;
    }

    visited.assign(N, false);
    parent.assign(N, -1);
    depth.assign(N, -1);
    up_weight.assign(N, -1);
    dfs(0, -1, 0);

    vector<mod_int> ways(1 << BITS, 0);
    ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, ways);

    vector<check_int> check_ways(1 << BITS, 0);
    check_ways[0] = 1;
    walsh_hadamard::fwht_xor(1 << BITS, check_ways);

    for (vector<int> &cycle : cycles) {
        vector<mod_int> freq(1 << BITS, 0);
        vector<check_int> check_freq(1 << BITS, 0);

        for (int weight : cycle) {
            freq[weight]++;
            check_freq[weight]++;
        }

        walsh_hadamard::fwht_xor(1 << BITS, freq);
        walsh_hadamard::fwht_xor(1 << BITS, check_freq);

        for (int i = 0; i < 1 << BITS; i++) {
            ways[i] *= freq[i];
            check_ways[i] *= check_freq[i];
        }
    }

    walsh_hadamard::fwht_xor(1 << BITS, ways, true);
    walsh_hadamard::fwht_xor(1 << BITS, check_ways, true);

    int best = 1 << BITS;
    mod_int best_count = 0;

    for (int option = 0; option < 1 << BITS; option++)
        if ((ways[option] != 0 || check_ways[option] != 0) && (option ^ all_xor) < best) {
            best = option ^ all_xor;
            best_count = ways[option];
        }

    cout << best << ' ' << best_count << '\n';
}


#ifdef ONPC
# define _GLIBCXX_DEBUG
#define deb(a) cerr << "yo " << #a << " = " << a << endl;
#else
#define deb(a)
#endif
#include <bits/stdc++.h>
#define endl "\n"

using namespace std;

typedef int ll;
typedef long double ld;
ll MOD = 1e9 + 7;
ll INF = 1e18;

const int L = 19;
const int N = (1 << L);
vector<ll> gr[N];
vector<ll> gr2[N];

ll kol_c = 0;
ll comp[N];
ll h[N];
ll used[N];
ll dfs(ll v, ll pr){
    h[v] = h[pr] + 1;
    ll an = MOD;
    for (auto i : gr[v]){
        if (!h[i]){
            an = min(an, dfs(i, v));
        }
        else{
            if (i != pr)
                an = min(an, h[i]);
        }
    }
    comp[v] = (an >= h[v]);
    return an;
}
void cm(ll v, ll kol){
    if (comp[v])
        comp[v] = ++kol_c;
    else
        comp[v] = kol;
    used[v] = 1;
    for (auto i : gr[v]){
        if (!used[i]){
            cm(i, comp[v]);
        }
    }
}

vector<ll> num[N];

vector<ll> cnt, cnt2;

ll binpow(ll x, ll y)
{
    if (!y)
        return 1;
    if (y % 2)
        return ((long long)x * (long long)binpow(x, y - 1)) % MOD;
    return binpow(((long long)x * (long long)x) % MOD, y / 2);
}

ll revers(ll x)
{
    return binpow(x % MOD, MOD - 2);
}

void adamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    adamar(a, l, mid);
    adamar(a, mid, r);
    for (int i = l; i < mid; i++)
    {
        ll u = a[i], v = a[i + mid - l];
        a[i] = (u + v) % MOD;
        a[i + mid - l] = (u - v) % MOD;
    }
}

void andamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    andamar(a, l, mid);
    andamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] + a[i + mid - l])  % MOD;
}

void revandamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revandamar(a, l, mid);
    revandamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] - a[i + mid - l]) % MOD;
}

void ordamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    ordamar(a, l, mid);
    ordamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (a[i] + a[i + mid - l]) % MOD;
}

void revordamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revordamar(a, l, mid);
    revordamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (-a[i] + a[i + mid - l]) % MOD;
}

vector<ll> xormult(vector<ll> a, vector<ll> b)
{
    adamar(a, 0, a.size());
    adamar(b, 0, b.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)b[i])  % MOD;
    adamar(a, 0, a.size());
    ll revn = revers(a.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)revn) % MOD;
    return a;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0), cout << fixed << setprecision(20);

    ll n, m;
    cin >> n >> m;
    ll x = 0;
    for (int i = 0; i < m; i++){
        ll u, v, w;
        cin >> u >> v >> w;
        x ^= w;
        u--,v--;
        gr[u].push_back(v);
        gr2[u].push_back(w);
        gr[v].push_back(u);
        gr2[v].push_back(w);
    }
    dfs(0, 0);
    cm(0, 0);
    for (int i = 0; i < n; i++){
        for (int j = 0; j < gr[i].size(); j++){
            ll to = gr[i][j];
            ll w = gr2[i][j];
            if (i > to && comp[i] == comp[to]){
                num[comp[i]].push_back(w);
            }
        }
    }
    ///////////////////////////////////
    cnt.assign(N, 0);
    cnt[x] = 1;
    for (int ii = 0; ii < N; ii++){
        if (num[ii].size()) {
            cnt2.assign(N, 0);
            for (auto j : num[ii]) {
                cnt2[j]++;
            }
            cnt = xormult(cnt, cnt2);
        }
    }
    for (int i = 0; i < N; i++){
        if (cnt[i]){
            cout << i << " " << cnt[i];
            break;
        }
    }

#ifdef ONPC
    cerr << endl << "finished in " << clock() * 1.0 / CLOCKS_PER_SEC << " sec" << endl;
#endif
}
//egor lox


#ifdef ONPC
# define _GLIBCXX_DEBUG
#define deb(a) cerr << "yo " << #a << " = " << a << endl;
#else
#define deb(a)
#endif
#include <bits/stdc++.h>
#define endl "\n"

using namespace std;

typedef int ll;
typedef long double ld;
ll MOD = 1e9 + 7;
ll INF = 1e18;

const int L = 18;
const int N = (1 << L);
vector<ll> gr[N];
vector<ll> gr2[N];

ll kol_c = 0;
ll comp[N];
ll h[N];
ll used[N];
ll dfs(ll v, ll pr){
    h[v] = h[pr] + 1;
    ll an = MOD;
    for (auto i : gr[v]){
        if (!h[i]){
            an = min(an, dfs(i, v));
        }
        else{
            if (i != pr)
                an = min(an, h[i]);
        }
    }
    comp[v] = (an >= h[v]);
    return an;
}
void cm(ll v, ll kol){
    if (comp[v])
        comp[v] = ++kol_c;
    else
        comp[v] = kol;
    used[v] = 1;
    for (auto i : gr[v]){
        if (!used[i]){
            cm(i, comp[v]);
        }
    }
}

vector<ll> num[N];

vector<ll> cnt, cnt2;

ll binpow(ll x, ll y)
{
    if (!y)
        return 1;
    if (y % 2)
        return ((long long)x * (long long)binpow(x, y - 1)) % MOD;
    return binpow(((long long)x * (long long)x) % MOD, y / 2);
}

ll revers(ll x)
{
    return binpow(x % MOD, MOD - 2);
}

void adamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    adamar(a, l, mid);
    adamar(a, mid, r);
    for (int i = l; i < mid; i++)
    {
        ll u = a[i], v = a[i + mid - l];
        a[i] = (u + v) % MOD;
        a[i + mid - l] = (u - v) % MOD;
    }
}

void andamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    andamar(a, l, mid);
    andamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] + a[i + mid - l])  % MOD;
}

void revandamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revandamar(a, l, mid);
    revandamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] - a[i + mid - l]) % MOD;
}

void ordamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    ordamar(a, l, mid);
    ordamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (a[i] + a[i + mid - l]) % MOD;
}

void revordamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revordamar(a, l, mid);
    revordamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (-a[i] + a[i + mid - l]) % MOD;
}

vector<ll> xormult(vector<ll> a, vector<ll> b)
{
    adamar(a, 0, a.size());
    adamar(b, 0, b.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)b[i])  % MOD;
    adamar(a, 0, a.size());
    ll revn = revers(a.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)revn) % MOD;
    return a;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0), cout << fixed << setprecision(20);

    ll n, m;
    cin >> n >> m;
    ll x = 0;
    for (int i = 0; i < m; i++){
        ll u, v, w;
        cin >> u >> v >> w;
        x ^= w;
        u--,v--;
        gr[u].push_back(v);
        gr2[u].push_back(w);
        gr[v].push_back(u);
        gr2[v].push_back(w);
    }
    dfs(0, 0);
    cm(0, 0);
    for (int i = 0; i < n; i++){
        for (int j = 0; j < gr[i].size(); j++){
            ll to = gr[i][j];
            ll w = gr2[i][j];
            if (i > to && comp[i] == comp[to]){
                num[comp[i]].push_back(w);
            }
        }
    }
    ///////////////////////////////////
    cnt.assign(N, 0);
    cnt[x] = 1;
    for (int ii = 0; ii < N; ii++){
        if (num[ii].size()) {
            cnt2.assign(N, 0);
            for (auto j : num[ii]) {
                cnt2[j]++;
            }
            cnt = xormult(cnt, cnt2);
        }
    }
    for (int i = 0; i < N; i++){
        if (cnt[i]){
            cout << i << " " << (MOD + cnt[i]) % MOD;
            break;
        }
    }

#ifdef ONPC
    cerr << endl << "finished in " << clock() * 1.0 / CLOCKS_PER_SEC << " sec" << endl;
#endif
}
//egor lox


#ifdef ONPC
# define _GLIBCXX_DEBUG
#define deb(a) cerr << "yo " << #a << " = " << a << endl;
#else
#define deb(a)
#endif
#include <bits/stdc++.h>
#define endl "\n"

using namespace std;

typedef int ll;
typedef long double ld;
ll MOD = 1e9 + 7;
ll INF = 1e18;

const int L = 18;
const int N = (1 << L);
vector<ll> gr[N];
vector<ll> gr2[N];

ll kol_c = 0;
ll comp[N];
ll h[N];
ll used[N];
ll dfs(ll v, ll pr){
    h[v] = h[pr] + 1;
    ll an = MOD;
    for (auto i : gr[v]){
        if (!h[i]){
            an = min(an, dfs(i, v));
        }
        else{
            if (i != pr)
                an = min(an, h[i]);
        }
    }
    comp[v] = (an >= h[v]);
    return an;
}
void cm(ll v, ll kol){
    if (comp[v])
        comp[v] = ++kol_c;
    else
        comp[v] = kol;
    used[v] = 1;
    for (auto i : gr[v]){
        if (!used[i]){
            cm(i, comp[v]);
        }
    }
}

vector<ll> num[N];

vector<ll> cnt, cnt2;

ll binpow(ll x, ll y)
{
    if (!y)
        return 1;
    if (y % 2)
        return ((long long)x * (long long)binpow(x, y - 1)) % MOD;
    return binpow(((long long)x * (long long)x) % MOD, y / 2);
}

ll revers(ll x)
{
    return binpow(x % MOD, MOD - 2);
}

void adamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    adamar(a, l, mid);
    adamar(a, mid, r);
    for (int i = l; i < mid; i++)
    {
        ll u = a[i], v = a[i + mid - l];
        a[i] = (u + v) % MOD;
        a[i + mid - l] = (u - v) % MOD;
    }
}

void andamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    andamar(a, l, mid);
    andamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] + a[i + mid - l])  % MOD;
}

void revandamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revandamar(a, l, mid);
    revandamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] - a[i + mid - l]) % MOD;
}

void ordamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    ordamar(a, l, mid);
    ordamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (a[i] + a[i + mid - l]) % MOD;
}

void revordamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revordamar(a, l, mid);
    revordamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (-a[i] + a[i + mid - l]) % MOD;
}

vector<ll> xormult(vector<ll> a, vector<ll> b)
{
    adamar(a, 0, a.size());
    adamar(b, 0, b.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)b[i])  % MOD;
    adamar(a, 0, a.size());
    ll revn = revers(a.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)revn) % MOD;
    return a;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0), cout << fixed << setprecision(20);

    ll n, m;
    cin >> n >> m;
    ll x = 0;
    for (int i = 0; i < m; i++){
        ll u, v, w;
        cin >> u >> v >> w;
        x ^= w;
        u--,v--;
        gr[u].push_back(v);
        gr2[u].push_back(w);
        gr[v].push_back(u);
        gr2[v].push_back(w);
    }
    dfs(0, 0);
    cm(0, 0);
    for (int i = 0; i < n; i++){
        for (int j = 0; j < gr[i].size(); j++){
            ll to = gr[i][j];
            ll w = gr2[i][j];
            if (i > to && comp[i] == comp[to]){
                num[comp[i]].push_back(w);
            }
        }
    }
    ///////////////////////////////////
    cnt.assign(N, 0);
    cnt[x] = 1;
    for (int ii = 0; ii < N; ii++){
        if (num[ii].size()) {
            cnt2.assign(N, 0);
            for (auto j : num[ii]) {
                cnt2[j]++;
            }
            cnt = xormult(cnt, cnt2);
        }
    }
    for (int i = 0; i < N; i++){
        if (cnt[i]){
            cout << i << " " << cnt[i];
            break;
        }
    }

#ifdef ONPC
    cerr << endl << "finished in " << clock() * 1.0 / CLOCKS_PER_SEC << " sec" << endl;
#endif
}
//egor lox


#ifdef ONPC
# define _GLIBCXX_DEBUG
#define deb(a) cerr << "yo " << #a << " = " << a << endl;
#else
#define deb(a)
#endif
#include <bits/stdc++.h>
#define endl "\n"

using namespace std;

typedef int ll;
typedef long double ld;
ll MOD = 1e9 + 7;
ll MOD2 = 998244353;
ll INF = 1e18;

const int L = 19;
const int N = (1 << L);
vector<ll> gr[N];
vector<ll> gr2[N];

ll kol_c = 0;
ll comp[N];
ll h[N];
ll used[N];
ll dfs(ll v, ll pr){
    h[v] = h[pr] + 1;
    ll an = MOD;
    for (auto i : gr[v]){
        if (!h[i]){
            an = min(an, dfs(i, v));
        }
        else{
            if (i != pr)
                an = min(an, h[i]);
        }
    }
    comp[v] = (an >= h[v]);
    return an;
}
void cm(ll v, ll kol){
    if (comp[v])
        comp[v] = ++kol_c;
    else
        comp[v] = kol;
    used[v] = 1;
    for (auto i : gr[v]){
        if (!used[i]){
            cm(i, comp[v]);
        }
    }
}

vector<ll> num[N];

vector<ll> cnt, cnt2;
vector<ll> cnt_, cnt2_;

ll binpow(ll x, ll y)
{
    if (!y)
        return 1;
    if (y % 2)
        return (1ll * (long long)x * (long long)binpow(x, y - 1)) % MOD;
    return binpow((1ll * (long long)x * (long long)x) % MOD, y / 2);
}

ll revers(ll x)
{
    return binpow(x % MOD, MOD - 2);
}

void adamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    adamar(a, l, mid);
    adamar(a, mid, r);
    for (int i = l; i < mid; i++)
    {
        ll u = a[i], v = a[i + mid - l];
        a[i] = (u + v) % MOD;
        a[i + mid - l] = (u - v) % MOD;
    }
}

void andamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    andamar(a, l, mid);
    andamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] + a[i + mid - l])  % MOD;
}

void revandamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revandamar(a, l, mid);
    revandamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] - a[i + mid - l]) % MOD;
}

void ordamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    ordamar(a, l, mid);
    ordamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (a[i] + a[i + mid - l]) % MOD;
}

void revordamar(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revordamar(a, l, mid);
    revordamar(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (-a[i] + a[i + mid - l]) % MOD;
}

vector<ll> xormult(vector<ll> a, vector<ll> b)
{
    adamar(a, 0, a.size());
    adamar(b, 0, b.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)b[i])  % MOD;
    adamar(a, 0, a.size());
    ll revn = revers(a.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)revn) % MOD;
    return a;
}

ll binpow2(ll x, ll y)
{
    if (!y)
        return 1;
    if (y % 2)
        return (1ll * (long long)x * (long long)binpow2(x, y - 1)) % MOD2;
    return binpow2((1ll * (long long)x * (long long)x) % MOD2, y / 2);
}

ll revers2(ll x)
{
    return binpow2(x % MOD2, MOD2 - 2);
}

void adamar2(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    adamar2(a, l, mid);
    adamar2(a, mid, r);
    for (int i = l; i < mid; i++)
    {
        ll u = a[i], v = a[i + mid - l];
        a[i] = (u + v) % MOD2;
        a[i + mid - l] = (u - v) % MOD2;
    }
}

void andamar2(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    andamar2(a, l, mid);
    andamar2(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] + a[i + mid - l])  % MOD2;
}

void revandamar2(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revandamar2(a, l, mid);
    revandamar2(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i] = (a[i] - a[i + mid - l]) % MOD2;
}

void ordamar2(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    ordamar2(a, l, mid);
    ordamar2(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (a[i] + a[i + mid - l]) % MOD2;
}

void revordamar2(vector<ll> &a, int l, int r)
{
    if (l + 1 >= r)
        return;
    int mid = (r + l) / 2;
    revordamar2(a, l, mid);
    revordamar2(a, mid, r);
    for (int i = l; i < mid; i++)
        a[i + mid - l] = (-a[i] + a[i + mid - l]) % MOD2;
}

vector<ll> xormult2(vector<ll> a, vector<ll> b)
{
    adamar2(a, 0, a.size());
    adamar2(b, 0, b.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)b[i])  % MOD2;
    adamar2(a, 0, a.size());
    ll revn = revers2(a.size());
    for (int i = 0; i < a.size(); i++)
        a[i] = (1ll * (long long)a[i] * (long long)revn) % MOD2;
    return a;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0), cout << fixed << setprecision(20);

    ll n, m;
    cin >> n >> m;
    ll x = 0;
    for (int i = 0; i < m; i++){
        ll u, v, w;
        cin >> u >> v >> w;
        x ^= w;
        u--,v--;
        gr[u].push_back(v);
        gr2[u].push_back(w);
        gr[v].push_back(u);
        gr2[v].push_back(w);
    }
    dfs(0, 0);
    cm(0, 0);
    for (int i = 0; i < n; i++){
        for (int j = 0; j < gr[i].size(); j++){
            ll to = gr[i][j];
            ll w = gr2[i][j];
            if (i > to && comp[i] == comp[to]){
                num[comp[i]].push_back(w);
            }
        }
    }
//    for (int i = 0; i < N; i++){
//        num[i].push_back(0);
//    }
    ///////////////////////////////////
    cnt.assign(N, 0);
    cnt_.assign(N, 0);
    cnt[x] = 1;
    cnt_[x] = 1;
    for (int ii = 0; ii < N; ii++){
        if (num[ii].size()) {
            cnt2.assign(N, 0);
            cnt2_.assign(N, 0);
            for (auto j : num[ii]) {
                cnt2[j]++;
                cnt2_[j]++;
            }
            cnt = xormult(cnt, cnt2);
            cnt_ = xormult2(cnt_, cnt2_);
            for (int i = 0; i < N; i++){
                cnt[i] %= MOD;
                cnt_[i] %= MOD2;
                if (cnt[i] < 0){
                    cnt[i] += MOD;
                }
                if (cnt_[i] < 0){
                    cnt_[i] += MOD2;
                }
            }
        }
    }
    for (int i = 0; i < N; i++){
        if (cnt[i] || cnt_[i]){
            cout << i << " " << cnt[i];
            break;
        }
    }

#ifdef ONPC
    cerr << endl << "finished in " << clock() * 1.0 / CLOCKS_PER_SEC << " sec" << endl;
#endif
}
//egor lox