//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int inf_int = 1e9 + 100;
const ll inf_ll = 1e18;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef long double dbl;
#define pb push_back
#define eb emplace_back
const double pi = 3.1415926535898;
#define dout if(debug) cout
#define fi first
#define se second
#define sp setprecision
#define sz(a) (int(a.size()))
#define mp make_pair
#define all(a) a.begin(),a.end()
bool debug = 0;
const int MAXN = 1e5 + 100;
const int LOG = 21;
const int mod = 998244353;
const int MX = 1e3 + 100;
typedef long long li;
const li MOD = 1000000000949747713ll;

template<class T1, class T2>
std::ostream &operator<<(std::ostream &out, const std::pair<T1, T2> &rhs) {
    out << "( " << rhs.first << " , " << rhs.second << " )";
    return out;
}

ll bin_pow(ll a, int n) {
    ll res = 1;
    while (n) {
        if (n & 1) {
            res = (res * a) % mod;
        }
        a = (a * a) % mod;
        n >>= 1;
    }
    return res;
}

ll reverse(ll a) {
    return bin_pow(a, mod - 2);
}

vector<int> FWHT(vector<int> &P, bool inverse) {
    int u, v;
    int deg = P.size();
    int i;
    for (int len = 1; 2 * len <= deg; len <<= 1) {
        for (i = 0; i < deg; i += 2 * len) {
            for (int j = 0; j < len; j++) {
                u = P[i + j];
                v = P[i + len + j];
                P[i + j] = u + v;
                if (P[i + j] >= mod)
                    P[i + j] -= mod;
                P[i + len + j] = u - v;
                if (P[i + len + j] < 0)
                    P[i + len + j] += mod;
            }
        }
    }

    if (inverse) {
        ll mul = reverse(deg);
        for (i = 0; i < deg; i++) {
            P[i] = (P[i] * mul) % mod;
        }
    }
    return P;
}

vector<pii> g[MAXN];
bool used[MAXN];
int timer, tin[MAXN], fup[MAXN];

int used_bridge[MAXN];

void dfs(int v, int p = -1) {
    used[v] = true;
    tin[v] = fup[v] = timer++;
    for (size_t i = 0; i < g[v].size(); ++i) {
        int to = g[v][i].fi;
        int id = g[v][i].se;
        if (to == p) continue;
        if (used[to])
            fup[v] = min(fup[v], tin[to]);
        else {
            dfs(to, v);
            fup[v] = min(fup[v], fup[to]);
            if (fup[to] > tin[v]) {
                used_bridge[id] = 1;
            }
        }
    }
}

int n, m;

void find_bridges() {
    timer = 0;
    for (int i = 1; i <= n; ++i)
        used[i] = false;
    for (int i = 1; i <= n; ++i)
        if (!used[i])
            dfs(i);
}

int x[MAXN], y[MAXN], c[MAXN];

vector<int> ids;

void dfs2(int v) {
    used[v] = true;
    for (pii x:g[v]) {
        int to = x.fi;
        int id = x.se;
        if (!used_bridge[id]) {
            used_bridge[id] = 1;
            ids.pb(id);
        }
        if (!used[to]) {
            dfs2(to);
        }
    }
}

void solve() {

    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int a, b;
        cin >> a >> b;
        g[a].pb({b, i});
        g[b].pb({a, i});
        x[i] = a;
        y[i] = b;
        cin >> c[i];
    }
    find_bridges();

    for (int i = 1; i <= n; ++i) {
        g[i].clear();
        used[i] = false;
    }
    int val = 0;
    for (int i = 1; i <= m; ++i) {
        if (used_bridge[i]) {
     //       dout << "bridge " << i << " " << c[i] << endl;
            val = val ^ c[i];
        } else {
            g[x[i]].pb({y[i], i});
            g[y[i]].pb({x[i], i});
        }
    }
    const int MX_C = 1 << 17;

    vector<int> res(MX_C);
   // dout << "val " << val << endl;
    res[val] = 1;

    FWHT(res, false);

    for (int i = 1; i <= n; ++i) {
        if (!used[i]) {
            ids.clear();
            dfs2(i);
            if (sz(ids) > 0) {
                vector<int> cur(MX_C);
                int all = 0;
                for (int v:ids) {
                    all ^= c[v];
                }
            /*    dout << "HERE v ; ";
                for (int v:ids) {
                    dout << v << " ";
                }
                dout << endl;*/

                for (int v:ids) {
                    cur[all ^ c[v]]++;
                }

                FWHT(cur, false);

                for (int i = 0; i < MX_C; ++i) {
                    res[i] = (1ll * res[i] * cur[i]) % mod;
                }

            }
        }
    }

    FWHT(res, true);
    for (int i = 0; i < MX_C; ++i) {
        if (res[i] != 0) {
            cout << i << " " << res[i] << "\n";
            return;
        }
    }
    cout << "NO";

}

signed main() {
#ifdef zxc
    debug = 1;
    freopen("../input.txt", "r", stdin);
    // freopen("../output.txt", "w", stdout);
#else

#endif //zxc
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cout.setf(ios::fixed);
    cout.precision(20);

    int t = 1;
    while (t--)
        solve();
    if (debug)
        cerr << endl << "time : " << (1.0 * clock() / CLOCKS_PER_SEC) << endl;
}


//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const int inf_int = 1e9 + 100;
const ll inf_ll = 1e18;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef long double dbl;
#define pb push_back
#define eb emplace_back
const double pi = 3.1415926535898;
#define dout if(debug) cout
#define fi first
#define se second
#define sp setprecision
#define sz(a) (int(a.size()))
#define mp make_pair
#define all(a) a.begin(),a.end()
bool debug = 0;
const int MAXN = 2e5 + 100;
const int LOG = 21;
const int mod = 1000000007;
const int MX = 1e3 + 100;
typedef long long li;
const li MOD = 1000000007;

template<class T1, class T2>
std::ostream &operator<<(std::ostream &out, const std::pair<T1, T2> &rhs) {
    out << "( " << rhs.first << " , " << rhs.second << " )";
    return out;
}

ll bin_pow(ll a, int n) {
    ll res = 1;
    while (n) {
        if (n & 1) {
            res = (res * a) % mod;
        }
        a = (a * a) % mod;
        n >>= 1;
    }
    return res;
}

ll reverse(ll a) {
    return bin_pow(a, mod - 2);
}

vector<int> FWHT(vector<int> &P, bool inverse) {
    int u, v;
    int deg = P.size();
    int i;
    for (int len = 1; 2 * len <= deg; len <<= 1) {
        for (i = 0; i < deg; i += 2 * len) {
            for (int j = 0; j < len; j++) {
                u = P[i + j];
                v = P[i + len + j];
                P[i + j] = u + v;
                if (P[i + j] >= mod)
                    P[i + j] -= mod;
                P[i + len + j] = u - v;
                if (P[i + len + j] < 0)
                    P[i + len + j] += mod;
            }
        }
    }

    if (inverse) {
        ll mul = reverse(deg);
        for (i = 0; i < deg; i++) {
            P[i] = (P[i] * mul) % mod;
        }
    }
    return P;
}

vector<pii> g[MAXN];
bool used[MAXN];
int timer, tin[MAXN], fup[MAXN];

int used_bridge[MAXN];

void dfs(int v, int p = -1) {
    used[v] = true;
    tin[v] = fup[v] = timer++;
    for (size_t i = 0; i < g[v].size(); ++i) {
        int to = g[v][i].fi;
        int id = g[v][i].se;
        if (to == p) continue;
        if (used[to])
            fup[v] = min(fup[v], tin[to]);
        else {
            dfs(to, v);
            fup[v] = min(fup[v], fup[to]);
            if (fup[to] > tin[v]) {
                used_bridge[id] = 1;
            }
        }
    }
}

int n, m;

void find_bridges() {
    timer = 0;
    for (int i = 1; i <= n; ++i)
        used[i] = false;
    for (int i = 1; i <= n; ++i)
        if (!used[i])
            dfs(i);
}

int x[MAXN], y[MAXN], c[MAXN];

vector<int> ids;

void dfs2(int v) {
    used[v] = true;
    for (pii x:g[v]) {
        int to = x.fi;
        int id = x.se;
        if (!used_bridge[id]) {
            used_bridge[id] = 1;
            ids.pb(id);
        }
        if (!used[to]) {
            dfs2(to);
        }
    }
}

void solve() {

    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int a, b;
        cin >> a >> b;
        g[a].pb({b, i});
        g[b].pb({a, i});
        x[i] = a;
        y[i] = b;
        cin >> c[i];
    }
    find_bridges();

    for (int i = 1; i <= n; ++i) {
        g[i].clear();
        used[i] = false;
    }
    int val = 0;
    for (int i = 1; i <= m; ++i) {
        if (used_bridge[i]) {
            val = val ^ c[i];
        } else {
            g[x[i]].pb({y[i], i});
            g[y[i]].pb({x[i], i});
        }
    }
    const int MX_C = 1 << 18;

    vector<int> res(MX_C);
    res[val] = 1;

    FWHT(res, false);

    for (int i = 1; i <= n; ++i) {
        if (!used[i]) {
            ids.clear();
            dfs2(i);
            if (sz(ids) > 0) {
                vector<int> cur(MX_C);
                int all = 0;
                for (int v:ids) {
                    all ^= c[v];
                }

                for (int v:ids) {
                    cur[all ^ c[v]]++;
                }

                FWHT(cur, false);

                for (int i = 0; i < MX_C; ++i) {
                    res[i] = (1ll * res[i] * cur[i]) % mod;
                }

            }
        }
    }

    FWHT(res, true);
    for (int i = 0; i < MX_C; ++i) {
        if (res[i] != 0) {
            cout << i << " " << res[i] << "\n";
            return;
        }
    }
    cout << "NO";

}

signed main() {
#ifdef zxc
    debug = 1;
    freopen("../input.txt", "r", stdin);
    // freopen("../output.txt", "w", stdout);
#else

#endif //zxc
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cout.setf(ios::fixed);
    cout.precision(20);

    int t = 1;
    while (t--)
        solve();
    if (debug)
        cerr << endl << "time : " << (1.0 * clock() / CLOCKS_PER_SEC) << endl;
}


// Dmitry _kun_ Sayutin (2019)

#include <bits/stdc++.h>

using std::cin;
using std::cout;
using std::cerr;

using std::vector;
using std::map;
using std::array;
using std::set;
using std::string;

using std::pair;
using std::make_pair;

using std::tuple;
using std::make_tuple;
using std::get;

using std::min;
using std::abs;
using std::max;
using std::swap;

using std::unique;
using std::sort;
using std::generate;
using std::reverse;
using std::min_element;
using std::max_element;

#ifdef LOCAL
#define LASSERT(X) assert(X)
#else
#define LASSERT(X) {}
#endif

template <typename T>
T input() {
    T res;
    cin >> res;
    LASSERT(cin);
    return res;
}

template <typename IT>
void input_seq(IT b, IT e) {
    std::generate(b, e, input<typename std::remove_reference<decltype(*b)>::type>);
}

#define SZ(vec)         int((vec).size())
#define ALL(data)       data.begin(),data.end()
#define RALL(data)      data.rbegin(),data.rend()
#define TYPEMAX(type)   std::numeric_limits<type>::max()
#define TYPEMIN(type)   std::numeric_limits<type>::min()

struct edge_t {
    int to;
    int w;
};

int fpow(int a, int n, int mod, int r = 1) {
    while (n) {
        if (n % 2)
            r = (int64_t(r) * a) % mod;

        a = (int64_t(a) * a) % mod;
        n /= 2;
    }

    return r;
}

void fft(int k, vector<int>& arr, int mod) {
    for (int lvl = 0; lvl < k; ++lvl)
        for (int off = 0; off < (1 << k); off += (2 << lvl))
            for (int pos = 0; pos < (1 << lvl); ++pos) {
                int a = arr[off + pos];
                int b = arr[off + pos + (1 << lvl)];

                arr[off + pos] = (a + b >= mod ? a + b - mod : a + b);
                arr[off + pos + (1 << lvl)] = (a >= b ? a - b : mod + a - b);
            }
}

int main() {
    std::iostream::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // code here
    int n, m;
    cin >> n >> m;

    vector<vector<edge_t>> graph(n);
    int base = 0;
    for (int i = 0; i != m; ++i) {
        int v, u, w;
        cin >> v >> u >> w;
        --v, --u;

        graph[v].emplace_back(edge_t {u, w});
        graph[u].emplace_back(edge_t {v, w});
        base ^= w;
    }

    vector<edge_t> up(n, edge_t {-1,-1});
    vector<char> used(n, 0);

    vector<vector<int>> lists;

    std::function<void(int, edge_t)> dfs = [&](int v, edge_t p) {
        up[v] = p;
        used[v] = 1;

        for (auto u: graph[v])
            if (used[u.to] == 0) {
                dfs(u.to, edge_t {v, u.w});
            } else if (used[u.to] == 1 and u.to != p.to) {
                // kukareku

                vector<int> list = {u.w};
                for (int w = v; w != u.to; w = up[w].to)
                    list.push_back(up[w].w);

                int xr = 0;
                for (int elem: list)
                    xr ^= elem;

                base ^= xr;
                for (int& elem: list)
                    elem ^= xr;

                lists.push_back(list);
            }

        used[v] = 2;
    };

    dfs(0, edge_t {-10, -10});
    lists.push_back(vector<int> {base});

    // for (auto lst: lists) {
    //     for (int elem: lst)
    //         cout << elem << " ";
    //     cout << "\n";
    // }

    int mod = int(1e9) + 993;
    int m2 = int(1e9) + 7;

    vector<int> arr(1 << 17, 1);
    vector<int> arr2(1 << 17, 1);

    for (auto& el: lists) {
        vector<int> r1(1 << 17), r2(1 << 17);
        for (int v: el) {
            r1[v] += 1;
            r2[v] += 1;
        }

        fft(17, r1, mod);
        fft(17, r2, m2);

        for (int i = 0; i != (1 << 17); ++i) {
            arr[i] = (int64_t(arr[i]) * r1[i]) % mod;
            arr2[i] = (int64_t(arr2[i]) * r2[i]) % m2;
        }
    }

    fft(17, arr, mod);
    fft(17, arr2, m2);

    int minpos = 0;
    while (arr[minpos] == 0 and arr2[minpos] == 0)
        ++minpos;

    cout << minpos << " " << fpow(1 << 17, m2 - 2, m2, arr2[minpos]) << "\n";

    return 0;
}


// Dmitry _kun_ Sayutin (2019)

#include <bits/stdc++.h>

using std::cin;
using std::cout;
using std::cerr;

using std::vector;
using std::map;
using std::array;
using std::set;
using std::string;

using std::pair;
using std::make_pair;

using std::tuple;
using std::make_tuple;
using std::get;

using std::min;
using std::abs;
using std::max;
using std::swap;

using std::unique;
using std::sort;
using std::generate;
using std::reverse;
using std::min_element;
using std::max_element;

#ifdef LOCAL
#define LASSERT(X) assert(X)
#else
#define LASSERT(X) {}
#endif

template <typename T>
T input() {
    T res;
    cin >> res;
    LASSERT(cin);
    return res;
}

template <typename IT>
void input_seq(IT b, IT e) {
    std::generate(b, e, input<typename std::remove_reference<decltype(*b)>::type>);
}

#define SZ(vec)         int((vec).size())
#define ALL(data)       data.begin(),data.end()
#define RALL(data)      data.rbegin(),data.rend()
#define TYPEMAX(type)   std::numeric_limits<type>::max()
#define TYPEMIN(type)   std::numeric_limits<type>::min()

struct edge_t {
    int to;
    int w;
};

int fpow(int a, int n, int mod, int r = 1) {
    while (n) {
        if (n % 2)
            r = (int64_t(r) * a) % mod;

        a = (int64_t(a) * a) % mod;
        n /= 2;
    }

    return r;
}

void fft(int k, vector<int>& arr, int mod) {
    for (int lvl = 0; lvl < k; ++lvl)
        for (int off = 0; off < (1 << k); off += (2 << lvl))
            for (int pos = 0; pos < (1 << lvl); ++pos) {
                int a = arr[off + pos];
                int b = arr[off + pos + (1 << lvl)];

                arr[off + pos] = (a + b >= mod ? a + b - mod : a + b);
                arr[off + pos + (1 << lvl)] = (a >= b ? a - b : mod + a - b);
            }
}

int main() {
    std::iostream::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // code here
    int n, m;
    cin >> n >> m;

    vector<vector<edge_t>> graph(n);
    int base = 0;
    for (int i = 0; i != m; ++i) {
        int v, u, w;
        cin >> v >> u >> w;
        --v, --u;

        graph[v].emplace_back(edge_t {u, w});
        graph[u].emplace_back(edge_t {v, w});
        base ^= w;
    }

    vector<edge_t> up(n, edge_t {-1,-1});
    vector<char> used(n, 0);

    vector<vector<int>> lists;

    std::function<void(int, edge_t)> dfs = [&](int v, edge_t p) {
        up[v] = p;
        used[v] = 1;

        for (auto u: graph[v])
            if (used[u.to] == 0) {
                dfs(u.to, edge_t {v, u.w});
            } else if (used[u.to] == 1 and u.to != p.to) {
                // kukareku

                vector<int> list = {u.w};
                for (int w = v; w != u.to; w = up[w].to)
                    list.push_back(up[w].w);

                int xr = 0;
                for (int elem: list)
                    xr ^= elem;

                base ^= xr;
                for (int& elem: list)
                    elem ^= xr;

                lists.push_back(list);
            }

        used[v] = 2;
    };

    dfs(0, edge_t {-10, -10});
    lists.push_back(vector<int> {base});

    for (auto lst: lists) {
        for (int elem: lst)
            cout << elem << " ";
        cout << "\n";
    }

    int mod = int(1e9) + 993;
    int m2 = int(1e9) + 7;

    vector<int> arr(1 << 17, 1);
    vector<int> arr2(1 << 17, 1);

    for (auto& el: lists) {
        vector<int> r1(1 << 17), r2(1 << 17);
        for (int v: el) {
            r1[v] += 1;
            r2[v] += 1;
        }

        fft(17, r1, mod);
        fft(17, r2, m2);

        for (int i = 0; i != (1 << 17); ++i) {
            arr[i] = (int64_t(arr[i]) * r1[i]) % mod;
            arr2[i] = (int64_t(arr2[i]) * r2[i]) % m2;
        }
    }

    fft(17, arr, mod);
    fft(17, arr2, m2);

    int minpos = 0;
    while (arr[minpos] == 0 and arr2[minpos] == 0)
        ++minpos;

    cout << minpos << " " << fpow(1 << 17, m2 - 2, m2, arr2[minpos]) << "\n";

    return 0;
}


#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
using namespace chrono;
#define all(a) a.begin(), a.end()
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<double> vd;
typedef vector<string> vs;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<int, ll> pil;
typedef pair<ll, int> pli;
typedef tuple<int, int, int> tpl;
template<class T>
using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
template<class T1, class T2>
istream &operator>>(istream &in, pair<T1, T2> &P){
	in >> P.first >> P.second;
	return in;
}
template<class T1, class T2>
ostream &operator<<(ostream &out, const pair<T1, T2> &P){
	out << "(" << P.first << ", " << P.second << ")";
	return out;
}
template<class T>
istream &operator>>(istream &in, vector<T> &arr){
	for(auto &x: arr) in >> x;
	return in;
}
template<class T>
ostream &operator<<(ostream &out, const vector<T> &arr){
	for(auto &x: arr) out << x << ' '; cout << "\n";
	return out;
}
template<class T>
istream &operator>>(istream &in, deque<T> &arr){
	for(auto &x: arr) in >> x;
	return in;
}
template<class T>
ostream &operator<<(ostream &out, const deque<T> &arr){
	for(auto &x: arr) out << x << ' '; cout << "\n";
	return out;
}
mt19937 rnd(time(0));

const ll mod = 1e9 + 7;

ll modexp(ll b, ll e, ll mod){
	ll res = 1;
	while(e){
		if(e & 1) (res *= b) %= mod;
		(b *= b) %= mod;
		e >>= 1;
	}
	return res;
}
ll modinv(ll b, ll mod){
	return modexp(b, mod - 2, mod);
}

template<class T>
vector<T> xort(const vector<T> &P, bool inverse){
	vector<T> p(P);
	int n = p.size();
	for(int len = 1; 2 * len <= n; len <<= 1){
		for(int i = 0; i < n; i += 2 * len){
			for(int j = 0; j < len; j ++){
				T u = p[i + j], v = p[i + j + len];
				p[i + j] = (u + v) % mod, p[i + j + len] = (u - v + mod) % mod;
			}
		}
	}
	if(inverse){
		for(int i = 0; i < n; i ++){
			(p[i] *= modinv(n, mod)) %= mod;
		}
	}
	return p;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n, m;
	cin >> n >> m;
	vector<vector<pii>> adj(n);
	for(int i = 0; i < m; i ++){
		int u, v, w;
		cin >> u >> v >> w, u --, v --;
		adj[u].push_back({v, w}), adj[v].push_back({u, w});
	}
	vector<pii> prev(n);
	vector<vi> cycle;
	vi color(n);
	int sum = 0;
	function<void(int, int)> dfs = [&](int u, int p){
		color[u] = 1;
		for(auto [v, w]: adj[u]){
			if(v - p){
				if(color[v] == 0){
					prev[v] = {u, w};
					sum ^= w;
					dfs(v, u);
				}
				else if(color[v] == 1){
					cycle.push_back({w});
					for(int t = u; t != v; t = prev[t].first){
						sum ^= prev[t].second;
						cycle.back().push_back(prev[t].second);
					}
				}
			}
		}
		color[u] = 2;
	};
	dfs(0, 0);
	int mx = 1024 * 128;
	vl d(mx, 1);
	for(auto &c: cycle){
		vl temp(mx);
		int t = accumulate(all(c), 0, bit_xor<int>());
		temp[t] ++;
		for(auto x: c){
			temp[t ^ x] ++;
		}
		temp = xort(temp, 0);
		for(int i = 0; i < mx; i ++){
			(d[i] *= temp[i]) %= mod;
		}
	}
	d = xort(d, 1);
	int res = 2e9;
	ll cnt;
	for(int i = 0; i < mx; i ++){
		if(d[i] && res > (i ^ sum)){
			res = i ^ sum;
			cnt = d[i];
		}
	}
	cout << res << " " << cnt;
	return 0;
}
/*
 3-(2)>6
 | ^
(3) \______
 |        |
 1-(5)>4 (4)
 |     ___|
(6)  /
 v /
 2-(1)>5
*/

//////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                                                                //
//                                              _____________                                       //
//             ++++++++++                ___------------------\\                                    //
//           +++`+``+`+`++++        ///`````````````````````````````\\                              //
//           ++`+`+``+++`++++  /////```````````````````````````````````\                            //
//           +++`++`+`+``+++/////`````````````````````````````````````````\                         //
//             +++`++`+``+///```````````|```````````````````````````````````\                       //
//          ____++++/++++/`````````````/````````|````````|```````````````````\                      //
//        /  /  / |    //``````````````|````````|```````|````````|````````````\                     //
//       /  /  /  | ///````````/```````|```````||```````|````````|``````\```````\                   //
//       | /  /   |///`````````|``````/````````|````````|````````|```````|```````\                  //
//       |/   |   |//``|```````|``````|````````|`````````|```````|```````|````````\                 //
//       /\___|__//`|``|```````|`    |      ``:|````````|:```````|```````|```|`````|                //
//      /     /  /``|``|``````|/     |        :|     ```:|```````|```````|``++````++                //
//     /     / //```|``|``````|      |        |:        :|    ```|```````|```++``++`\               //
//     |    /  /````|``|``````/    _.::::.    |          |      |    ````|```|`++`\`|               //
//     |   /   |````|``|`````|            `                    |       ``|```++``++`|               //
//     |  /    |````|``|`````|                                 :         |``++````++|               //
//     | /     /````|``|`````|                              _.-:::.      |..`|``.`|.|               //
//     |/     /`````|``|`````|                                     `     |```|````|`|               //
//    /|      |`````|``|`````|                    :'                    .|```|````|.|               //
//   / |      |`````|``|`````|                                         /|-|``|````|`|               //
//  /  |      |`````|```\````|                                        / ||```|````|``\              //
// /   |      |`````|````|```|::                                    /_| ||```|````|``|              //
//            |`````|````|```|:|:.        `.._                    .\___/:|```|````|``|              //
//            |`````\````|```|:|::-          ``:::....           -:|:|:::|```|````|``|              //
//            |``````|```|```|:|::`|.                          .:::|:|:::|```|````|``|              //
//             \`````|```|```|:|::/|--.                     .`:|:::|:|:::/```|````|``|              //
//              |````|```|```\:|:|:|-----             _..-:|:|:|:::|:|::|````|````|`/               //
//              |````|```|````\|:|:|-------.____.....------|/::|:::|:|::|````|````|`|               //
//              |````|```|\````\:|/\___________    ________/\--\:::|:|::|````/````|`|               //
//              |````\```| \```|:/-------------\  /----------\``\::|:|::|```/`````|`|               //
//              |`````|``|  \``|/---------------\/------------\_________|```|`````|`|               //
//                                                                                //
//////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                        Created by Aeren                                          //
//                                                                                //
//////////////////////////////////////////////////////////////////////////////////////////////////////


#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
using namespace chrono;
#define all(a) a.begin(), a.end()
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<double> vd;
typedef vector<string> vs;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<int, ll> pil;
typedef pair<ll, int> pli;
typedef tuple<int, int, int> tpl;
template<class T>
using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
template<class T1, class T2>
istream &operator>>(istream &in, pair<T1, T2> &P){
	in >> P.first >> P.second;
	return in;
}
template<class T1, class T2>
ostream &operator<<(ostream &out, const pair<T1, T2> &P){
	out << "(" << P.first << ", " << P.second << ")";
	return out;
}
template<class T>
istream &operator>>(istream &in, vector<T> &arr){
	for(auto &x: arr) in >> x;
	return in;
}
template<class T>
ostream &operator<<(ostream &out, const vector<T> &arr){
	for(auto &x: arr) out << x << ' '; cout << "\n";
	return out;
}
template<class T>
istream &operator>>(istream &in, deque<T> &arr){
	for(auto &x: arr) in >> x;
	return in;
}
template<class T>
ostream &operator<<(ostream &out, const deque<T> &arr){
	for(auto &x: arr) out << x << ' '; cout << "\n";
	return out;
}
mt19937 rnd(time(0));

const ll mod = 1e9 + 7;

ll modexp(ll b, ll e, ll mod){
	ll res = 1;
	while(e){
		if(e & 1) (res *= b) %= mod;
		(b *= b) %= mod;
		e >>= 1;
	}
	return res;
}
ll modinv(ll b, ll mod){
	return modexp(b, mod - 2, mod);
}

// XOR Transformation. Degree must be a power of two.
template<class T>
vector<T> xort(const vector<T> &P, bool inverse){
	vector<T> p(P);
	int n = p.size();
	for(int len = 1; 2 * len <= n; len <<= 1){
		for(int i = 0; i < n; i += 2 * len){
			for(int j = 0; j < len; j ++){
				T u = p[i + j], v = p[i + j + len];
				p[i + j] = (u + v) % mod, p[i + j + len] = (u - v + mod) % mod;
			}
		}
	}
	if(inverse){
		for(int i = 0; i < n; i ++){
			(p[i] *= modinv(n, mod)) %= mod;
		}
	}
	return p;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n, m;
	cin >> n >> m;
	vector<vector<pii>> adj(n);
	for(int i = 0; i < m; i ++){
		int u, v, w;
		cin >> u >> v >> w, u --, v --;
		adj[u].push_back({v, w}), adj[v].push_back({u, w});
	}
	vector<pii> prev(n);
	vector<vi> cycle;
	vi color(n);
	int sum = 0;
	function<void(int, int)> dfs = [&](int u, int p){
		color[u] = 1;
		for(auto [v, w]: adj[u]){
			if(v - p){
				if(color[v] == 0){
					prev[v] = {u, w};
					sum ^= w;
					dfs(v, u);
				}
				else if(color[v] == 1){
					cycle.push_back({w});
					for(int t = u; t != v; t = prev[t].first){
						sum ^= prev[t].second;
						cycle.back().push_back(prev[t].second);
					}
				}
			}
		}
		color[u] = 2;
	};
	dfs(0, 0);
	int mx = 1024 * 128;
	vl d(mx, 1);
	for(auto &c: cycle){
		vl temp(mx);
		int t = accumulate(all(c), 0, bit_xor<int>());
		for(auto x: c){
			temp[t ^ x] ++;
		}
		temp = xort(temp, 0);
		for(int i = 0; i < mx; i ++){
			(d[i] *= temp[i]) %= mod;
		}
	}
	d = xort(d, 1);
	int res = 2e9;
	ll cnt;
	for(int i = 0; i < mx; i ++){
		if(d[i] && res > (i ^ sum)){
			res = i ^ sum;
			cnt = d[i];
		}
	}
	cout << res << " " << cnt;
	return 0;
}
/*
 3-(2)>6
 | ^
(3) \______
 |        |
 1-(5)>4 (4)
 |     ___|
(6)  /
 v /
 2-(1)>5
*/

//////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                                                                //
//                                              _____________                                       //
//             ++++++++++                ___------------------\\                                    //
//           +++`+``+`+`++++        ///`````````````````````````````\\                              //
//           ++`+`+``+++`++++  /////```````````````````````````````````\                            //
//           +++`++`+`+``+++/////`````````````````````````````````````````\                         //
//             +++`++`+``+///```````````|```````````````````````````````````\                       //
//          ____++++/++++/`````````````/````````|````````|```````````````````\                      //
//        /  /  / |    //``````````````|````````|```````|````````|````````````\                     //
//       /  /  /  | ///````````/```````|```````||```````|````````|``````\```````\                   //
//       | /  /   |///`````````|``````/````````|````````|````````|```````|```````\                  //
//       |/   |   |//``|```````|``````|````````|`````````|```````|```````|````````\                 //
//       /\___|__//`|``|```````|`    |      ``:|````````|:```````|```````|```|`````|                //
//      /     /  /``|``|``````|/     |        :|     ```:|```````|```````|``++````++                //
//     /     / //```|``|``````|      |        |:        :|    ```|```````|```++``++`\               //
//     |    /  /````|``|``````/    _.::::.    |          |      |    ````|```|`++`\`|               //
//     |   /   |````|``|`````|            `                    |       ``|```++``++`|               //
//     |  /    |````|``|`````|                                 :         |``++````++|               //
//     | /     /````|``|`````|                              _.-:::.      |..`|``.`|.|               //
//     |/     /`````|``|`````|                                     `     |```|````|`|               //
//    /|      |`````|``|`````|                    :'                    .|```|````|.|               //
//   / |      |`````|``|`````|                                         /|-|``|````|`|               //
//  /  |      |`````|```\````|                                        / ||```|````|``\              //
// /   |      |`````|````|```|::                                    /_| ||```|````|``|              //
//            |`````|````|```|:|:.        `.._                    .\___/:|```|````|``|              //
//            |`````\````|```|:|::-          ``:::....           -:|:|:::|```|````|``|              //
//            |``````|```|```|:|::`|.                          .:::|:|:::|```|````|``|              //
//             \`````|```|```|:|::/|--.                     .`:|:::|:|:::/```|````|``|              //
//              |````|```|```\:|:|:|-----             _..-:|:|:|:::|:|::|````|````|`/               //
//              |````|```|````\|:|:|-------.____.....------|/::|:::|:|::|````|````|`|               //
//              |````|```|\````\:|/\___________    ________/\--\:::|:|::|````/````|`|               //
//              |````\```| \```|:/-------------\  /----------\``\::|:|::|```/`````|`|               //
//              |`````|``|  \``|/---------------\/------------\_________|```|`````|`|               //
//                                                                                //
//////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                        Created by Aeren                                          //
//                                                                                //
//////////////////////////////////////////////////////////////////////////////////////////////////////


#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
using namespace chrono;
#define all(a) a.begin(), a.end()
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<double> vd;
typedef vector<string> vs;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<int, ll> pil;
typedef pair<ll, int> pli;
typedef tuple<int, int, int> tpl;
template<class T>
using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
template<class T1, class T2>
istream &operator>>(istream &in, pair<T1, T2> &P){
	in >> P.first >> P.second;
	return in;
}
template<class T1, class T2>
ostream &operator<<(ostream &out, const pair<T1, T2> &P){
	out << "(" << P.first << ", " << P.second << ")";
	return out;
}
template<class T>
istream &operator>>(istream &in, vector<T> &arr){
	for(auto &x: arr) in >> x;
	return in;
}
template<class T>
ostream &operator<<(ostream &out, const vector<T> &arr){
	for(auto &x: arr) out << x << ' '; cout << "\n";
	return out;
}
template<class T>
istream &operator>>(istream &in, deque<T> &arr){
	for(auto &x: arr) in >> x;
	return in;
}
template<class T>
ostream &operator<<(ostream &out, const deque<T> &arr){
	for(auto &x: arr) out << x << ' '; cout << "\n";
	return out;
}
mt19937 rnd(time(0));

const ll mod = 1e9 + 7;

ll modexp(ll b, ll e, ll mod){
	ll res = 1;
	while(e){
		if(e & 1) (res *= b) %= mod;
		(b *= b) %= mod;
		e >>= 1;
	}
	return res;
}
ll modinv(ll b, ll mod){
	return modexp(b, mod - 2, mod);
}

template<class T>
vector<T> xort(const vector<T> &P, bool inverse){
	vector<T> p(P);
	int n = p.size();
	for(int len = 1; 2 * len <= n; len <<= 1){
		for(int i = 0; i < n; i += 2 * len){
			for(int j = 0; j < len; j ++){
				T u = p[i + j], v = p[i + j + len];
				p[i + j] = (u + v) % mod, p[i + j + len] = (u - v + mod) % mod;
			}
		}
	}
	if(inverse){
		for(int i = 0; i < n; i ++){
			(p[i] *= modinv(n, mod)) %= mod;
		}
	}
	return p;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int n, m;
	cin >> n >> m;
	vector<vector<pii>> adj(n);
	for(int i = 0; i < m; i ++){
		int u, v, w;
		cin >> u >> v >> w, u --, v --;
		adj[u].push_back({v, w}), adj[v].push_back({u, w});
	}
	vector<pii> prev(n);
	vector<vi> cycle;
	vi color(n);
	int sum = 0;
	function<void(int, int)> dfs = [&](int u, int p){
		color[u] = 1;
		for(auto [v, w]: adj[u]){
			if(v - p){
				if(color[v] == 0){
					prev[v] = {u, w};
					sum ^= w;
					dfs(v, u);
				}
				else if(color[v] == 1){
					cycle.push_back({w});
					for(int t = u; t != v; t = prev[t].first){
						sum ^= prev[t].second;
						cycle.back().push_back(prev[t].second);
					}
				}
			}
		}
		color[u] = 2;
	};
	dfs(0, 0);
	int mx = 1024 * 128;
	vl d(mx, 1), check(mx, 1);
	for(auto &c: cycle){
		vl temp(mx), tcheck(mx);
		int t = accumulate(all(c), 0, bit_xor<int>());
		for(auto x: c){
			temp[t ^ x] ++;
			tcheck[t ^ x] = 1;
		}
		temp = xort(temp, 0);
		tcheck = xort(tcheck, 0);
		for(int i = 0; i < mx; i ++){
			(d[i] *= temp[i]) %= mod;
			(check[i] *= temp[i]) %= mod;
		}
		check = xort(check, 1);
		for(auto &x: tcheck){
			if(x){
				x = 1;
			}
		}
		check = xort(check, 0);
	}
	d = xort(d, 1);
	check = xort(check, 1);
	int res = 2e9;
	ll cnt;
	for(int i = 0; i < mx; i ++){
		if(check[i] && res > (i ^ sum)){
			res = i ^ sum;
			cnt = d[i];
		}
	}
	cout << res << " " << cnt;
	return 0;
}
/*
 3-(2)>6
 | ^
(3) \______
 |        |
 1-(5)>4 (4)
 |     ___|
(6)  /
 v /
 2-(1)>5
*/

//////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                                                                //
//                                              _____________                                       //
//             ++++++++++                ___------------------\\                                    //
//           +++`+``+`+`++++        ///`````````````````````````````\\                              //
//           ++`+`+``+++`++++  /////```````````````````````````````````\                            //
//           +++`++`+`+``+++/////`````````````````````````````````````````\                         //
//             +++`++`+``+///```````````|```````````````````````````````````\                       //
//          ____++++/++++/`````````````/````````|````````|```````````````````\                      //
//        /  /  / |    //``````````````|````````|```````|````````|````````````\                     //
//       /  /  /  | ///````````/```````|```````||```````|````````|``````\```````\                   //
//       | /  /   |///`````````|``````/````````|````````|````````|```````|```````\                  //
//       |/   |   |//``|```````|``````|````````|`````````|```````|```````|````````\                 //
//       /\___|__//`|``|```````|`    |      ``:|````````|:```````|```````|```|`````|                //
//      /     /  /``|``|``````|/     |        :|     ```:|```````|```````|``++````++                //
//     /     / //```|``|``````|      |        |:        :|    ```|```````|```++``++`\               //
//     |    /  /````|``|``````/    _.::::.    |          |      |    ````|```|`++`\`|               //
//     |   /   |````|``|`````|            `                    |       ``|```++``++`|               //
//     |  /    |````|``|`````|                                 :         |``++````++|               //
//     | /     /````|``|`````|                              _.-:::.      |..`|``.`|.|               //
//     |/     /`````|``|`````|                                     `     |```|````|`|               //
//    /|      |`````|``|`````|                    :'                    .|```|````|.|               //
//   / |      |`````|``|`````|                                         /|-|``|````|`|               //
//  /  |      |`````|```\````|                                        / ||```|````|``\              //
// /   |      |`````|````|```|::                                    /_| ||```|````|``|              //
//            |`````|````|```|:|:.        `.._                    .\___/:|```|````|``|              //
//            |`````\````|```|:|::-          ``:::....           -:|:|:::|```|````|``|              //
//            |``````|```|```|:|::`|.                          .:::|:|:::|```|````|``|              //
//             \`````|```|```|:|::/|--.                     .`:|:::|:|:::/```|````|``|              //
//              |````|```|```\:|:|:|-----             _..-:|:|:|:::|:|::|````|````|`/               //
//              |````|```|````\|:|:|-------.____.....------|/::|:::|:|::|````|````|`|               //
//              |````|```|\````\:|/\___________    ________/\--\:::|:|::|````/````|`|               //
//              |````\```| \```|:/-------------\  /----------\``\::|:|::|```/`````|`|               //
//              |`````|``|  \``|/---------------\/------------\_________|```|`````|`|               //
//                                                                                //
//////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                        Created by Aeren                                          //
//                                                                                //
//////////////////////////////////////////////////////////////////////////////////////////////////////


#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
using namespace chrono;
#define all(a) a.begin(), a.end()
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<double> vd;
typedef vector<string> vs;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<int, ll> pil;
typedef pair<ll, int> pli;
typedef tuple<int, int, int> tpl;
template<class T>
using Tree = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
template<class T1, class T2>
istream &operator>>(istream &in, pair<T1, T2> &P){
	in >> P.first >> P.second;
	return in;
}
template<class T1, class T2>
ostream &operator<<(ostream &out, const pair<T1, T2> &P){
	out << "(" << P.first << ", " << P.second << ")";
	return out;
}
template<class T>
istream &operator>>(istream &in, vector<T> &arr){
	for(auto &x: arr) in >> x;
	return in;
}
template<class T>
ostream &operator<<(ostream &out, const vector<T> &arr){
	for(auto &x: arr) out << x << ' '; cout << "\n";
	return out;
}
template<class T>
istream &operator>>(istream &in, deque<T> &arr){
	for(auto &x: arr) in >> x;
	return in;
}
template<class T>
ostream &operator<<(ostream &out, const deque<T> &arr){
	for(auto &x: arr) out << x << ' '; cout << "\n";
	return out;
}
mt19937 rnd(time(0));

ll modexp(ll b, ll e, ll mod){
	ll res = 1;
	while(e){
		if(e & 1) (res *= b) %= mod;
		(b *= b) %= mod;
		e >>= 1;
	}
	return res;
}
ll modinv(ll b, ll mod){
	return modexp(b, mod - 2, mod);
}

template<class T>
vector<T> xort(const vector<T> &P, bool inverse, const ll mod){
	vector<T> p(P);
	int n = p.size();
	for(int len = 1; 2 * len <= n; len <<= 1){
		for(int i = 0; i < n; i += 2 * len){
			for(int j = 0; j < len; j ++){
				T u = p[i + j], v = p[i + j + len];
				p[i + j] = (u + v) % mod, p[i + j + len] = (u - v + mod) % mod;
			}
		}
	}
	if(inverse){
		for(int i = 0; i < n; i ++){
			(p[i] *= modinv(n, mod)) %= mod;
		}
	}
	return p;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	const ll mod1 = 1e9 + 7;
	const ll mod2 = 998244353;
	int n, m;
	cin >> n >> m;
	vector<vector<pii>> adj(n);
	for(int i = 0; i < m; i ++){
		int u, v, w;
		cin >> u >> v >> w, u --, v --;
		adj[u].push_back({v, w}), adj[v].push_back({u, w});
	}
	vector<pii> prev(n);
	vector<vi> cycle;
	vi color(n);
	int sum = 0;
	function<void(int, int)> dfs = [&](int u, int p){
		color[u] = 1;
		for(auto [v, w]: adj[u]){
			if(v - p){
				if(color[v] == 0){
					prev[v] = {u, w};
					sum ^= w;
					dfs(v, u);
				}
				else if(color[v] == 1){
					cycle.push_back({w});
					for(int t = u; t != v; t = prev[t].first){
						sum ^= prev[t].second;
						cycle.back().push_back(prev[t].second);
					}
				}
			}
		}
		color[u] = 2;
	};
	dfs(0, 0);
	int mx = 1024 * 128;
	vl d1(mx, 1), d2(mx, 1);
	for(auto &c: cycle){
		vl temp(mx), tt(mx);
		int t = accumulate(all(c), 0, bit_xor<int>());
		for(auto x: c){
			temp[t ^ x] ++;
		}
		tt = xort(temp, 0, mod2);
		temp = xort(temp, 0, mod1);
		for(int i = 0; i < mx; i ++){
			(d1[i] *= temp[i]) %= mod1;
			(d2[i] *= tt[i]) %= mod2;
		}
	}
	d1 = xort(d1, 1, mod1);
	d2 = xort(d2, 1, mod2);
	int res = 2e9;
	ll cnt;
	for(int i = 0; i < mx; i ++){
		if((d1[i] || d2[i]) && res > (i ^ sum)){
			res = i ^ sum;
			cnt = d1[i];
		}
	}
	cout << res << " " << cnt;
	return 0;
}
/*
 3-(2)>6
 | ^
(3) \______
 |        |
 1-(5)>4 (4)
 |     ___|
(6)  /
 v /
 2-(1)>5
*/

//////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                                                                //
//                                              _____________                                       //
//             ++++++++++                ___------------------\\                                    //
//           +++`+``+`+`++++        ///`````````````````````````````\\                              //
//           ++`+`+``+++`++++  /////```````````````````````````````````\                            //
//           +++`++`+`+``+++/////`````````````````````````````````````````\                         //
//             +++`++`+``+///```````````|```````````````````````````````````\                       //
//          ____++++/++++/`````````````/````````|````````|```````````````````\                      //
//        /  /  / |    //``````````````|````````|```````|````````|````````````\                     //
//       /  /  /  | ///````````/```````|```````||```````|````````|``````\```````\                   //
//       | /  /   |///`````````|``````/````````|````````|````````|```````|```````\                  //
//       |/   |   |//``|```````|``````|````````|`````````|```````|```````|````````\                 //
//       /\___|__//`|``|```````|`    |      ``:|````````|:```````|```````|```|`````|                //
//      /     /  /``|``|``````|/     |        :|     ```:|```````|```````|``++````++                //
//     /     / //```|``|``````|      |        |:        :|    ```|```````|```++``++`\               //
//     |    /  /````|``|``````/    _.::::.    |          |      |    ````|```|`++`\`|               //
//     |   /   |````|``|`````|            `                    |       ``|```++``++`|               //
//     |  /    |````|``|`````|                                 :         |``++````++|               //
//     | /     /````|``|`````|                              _.-:::.      |..`|``.`|.|               //
//     |/     /`````|``|`````|                                     `     |```|````|`|               //
//    /|      |`````|``|`````|                    :'                    .|```|````|.|               //
//   / |      |`````|``|`````|                                         /|-|``|````|`|               //
//  /  |      |`````|```\````|                                        / ||```|````|``\              //
// /   |      |`````|````|```|::                                    /_| ||```|````|``|              //
//            |`````|````|```|:|:.        `.._                    .\___/:|```|````|``|              //
//            |`````\````|```|:|::-          ``:::....           -:|:|:::|```|````|``|              //
//            |``````|```|```|:|::`|.                          .:::|:|:::|```|````|``|              //
//             \`````|```|```|:|::/|--.                     .`:|:::|:|:::/```|````|``|              //
//              |````|```|```\:|:|:|-----             _..-:|:|:|:::|:|::|````|````|`/               //
//              |````|```|````\|:|:|-------.____.....------|/::|:::|:|::|````|````|`|               //
//              |````|```|\````\:|/\___________    ________/\--\:::|:|::|````/````|`|               //
//              |````\```| \```|:/-------------\  /----------\``\::|:|::|```/`````|`|               //
//              |`````|``|  \``|/---------------\/------------\_________|```|`````|`|               //
//                                                                                //
//////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
//                                        Created by Aeren                                          //
//                                                                                //
//////////////////////////////////////////////////////////////////////////////////////////////////////


#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) {
            T inv = T(1) / n;

            if (inv != 0) {
                for (int i = 0; i < n; i++)
                    values[i] *= inv;
            } else {
                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;
    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>;

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

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, true);
        check_ways = walsh_hadamard::xor_convolution<check_int>(check_ways, check_freq, 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; }

    _m_int inv() const;

    _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>;

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

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

template<> mod_int mod_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];
}

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

    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;

    _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>;

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

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

template<> mod_int mod_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];
}

// 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;
    walsh_hadamard::fwht_xor(1 << BITS, ways);

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

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

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

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

    walsh_hadamard::fwht_xor(1 << BITS, ways, 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>;

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) {
            T inv = T(1) / n;

            if (inv != 0) {
                for (int i = 0; i < n; i++)
                    values[i] *= inv;
            } else {
                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';
}