#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using db = double;
using ld = long double;
template <typename T> using V = vector<T>;
template <typename T> using VV = vector<vector<T>>;
#define fs first
#define sc second
#define pb push_back
#define mp make_pair
#define mt make_tuple
#define eb emplace_back
#define lb lower_bound
#define ub upper_bound
#define all(v) (v).begin(), (v).end()
#define siz(v) (ll)(v).size()
#define rep(i, a, n) for(ll i = a; i < (ll)(n); ++i)
#define repr(i, a, n) for(ll i = n - 1; (ll)a <= i; --i)
#define ENDL '\n'
typedef pair<int, int> Pi;
typedef pair<ll, ll> PL;
constexpr ll mod = 1000000007;  // 998244353;
constexpr ll INF = 1000000099;
constexpr ll LINF = (ll)(1e18 + 99);
const ld PI = acos((ld)-1);
const vector<ll> dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
template <typename T, typename U> inline bool chmin(T& t, const U& u) {
  if(t > u) {
    t = u;
    return 1;
  }
  return 0;
}
template <typename T, typename U> inline bool chmax(T& t, const U& u) {
  if(t < u) {
    t = u;
    return 1;
  }
  return 0;
}
template <typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
inline void yes() { cout << "Yes" << ENDL; }
inline void no() { cout << "No" << ENDL; }

template <typename T, typename Y> inline T mpow(T a, Y n) {
  T res = 1;
  for(; n; n >>= 1) {
    if(n & 1) res = res * a;
    a = a * a;
  }
  return res;
}

template <typename T> V<T> prefix_sum(const V<T>& v) {
  int n = v.size();
  V<T> ret(n + 1);
  rep(i, 0, n) ret[i + 1] = ret[i] + v[i];
  return ret;
}

template <typename T> istream& operator>>(istream& is, vector<T>& vec) {
  for(auto&& x : vec) is >> x;
  return is;
}

template <typename T, typename Y>
ostream& operator<<(ostream& os, const pair<T, Y>& p) {
  return os << "{" << p.fs << "," << p.sc << "}";
}

template <typename T> ostream& operator<<(ostream& os, const V<T>& v) {
  os << "{";
  for(auto e : v) os << e << ",";
  return os << "}";
}

template <typename... Args> void debug(Args&... args) {
  for(auto const& x : {args...}) { cerr << x << ' '; }
  cerr << ENDL;
}

template <const long long MOD = 1000000007> struct Mint {
  using M = Mint;
  ll a;
  Mint(ll x = 0) {
    a = x % MOD;
    if(a < 0) a += MOD;
  }  // Mintで

  M pow(ll n) {
    M b = *this, r = 1 % MOD;
    while(n) {
      if(n & 1) r *= b;
      b *= b;
      n >>= 1;
    }
    return r;
  }

  M inv() { return pow(MOD - 2); }

  M& operator+=(M r) {
    a += r.a;
    if(a >= MOD) a -= MOD;
    return *this;
  }
  M& operator-=(M r) {
    a += MOD - r.a;
    if(a >= MOD) a -= MOD;
    return *this;
  }
  M& operator*=(M r) {
    a = 1LL * a * r.a % MOD;
    return *this;
  }
  M& operator/=(M r) { return (*this) *= r.inv(); }
  M operator+(M r) const { return M(a) += r; };
  M operator-(M r) const { return M(a) -= r; };
  M operator*(M r) const { return M(a) *= r; };
  M operator/(M r) const { return M(a) /= r; };
  M operator-() const { return M() - *this; }

  friend ostream& operator<<(ostream& os, const M& r) { return os << r.a; }
};
using M = Mint<mod>;

template <typename T> struct UnionFind {
  vector<T> Parent;

  UnionFind(T N) { Parent = vector<T>(N, -1); }

  T root(T A) {
    if(Parent[A] < 0) return A;
    return Parent[A] = root(Parent[A]);
  }

  T size(T A) { return -Parent[root(A)]; }

  bool connect(T A, T B) {
    A = root(A);
    B = root(B);
    if(A == B) return false;
    if(size(A) < size(B)) swap(A, B);
    Parent[A] += Parent[B];
    Parent[B] = A;

    return true;
  }

  bool same(T A, T B) { return root(A) == root(B); }
};

signed main() {
  cin.tie(0);
  cerr.tie(0);
  ios::sync_with_stdio(false);
  ll n, m;
  cin >> n >> m;
  M ans = 1;
  V<int> ansid(0);
  V<bool> free(m, false);

  V<V<Pi>> e(m);
  V<tuple<int,int,int>> es(0);

  auto dfs = [&](auto self, int now) -> void {
    for(auto&& p : e[now]) {
      int to=p.fs;
      if(free[to]) continue;
      free[to] = true;
      ansid.eb(p.sc);
      self(self, to);
    }
  };

  rep(i, 0, n) {
    int k;
    cin >> k;
    if(k == 1) {
      int a;
      cin >> a;
      --a;
      if(free[a])continue;

      free[a] = true;
      dfs(dfs,a);
      ansid.eb(i);
    } else {
      int a, b;
      cin >> a >> b;
      --a;
      --b;
      if(free[a] || free[b]){
        if(!free[a]){
          free[a]=true;
          ansid.eb(i);
          dfs(dfs,a);
        }else if(!free[b]){
          free[b]=true;
          ansid.eb(i);
          dfs(dfs,b);
        }
      }else{
        e[a].eb(b,i);
        e[b].eb(a,i);
        es.eb(i,a,b);
      }
    }
  //debug(free);
  }

  UnionFind<int> u(m);
  V<int> rem(m,0);
  rep(i, 0, m) {
    if(free[i])
      ans *= 2;
    else {
      for(auto&& to : e[i]) { u.connect(i, to.fs); }
    }
  }

  rep(i, 0, m) {
    if(!free[i] && u.root(i) == i) {
      ans *= M(2).pow(u.size(i)-1);
      rem[i]=u.size(i)-1;
    }
  }

  UnionFind<int> u2(m);
  rep(i,0,siz(es)){
    int id,a,b;
    tie(id,a,b)=es[i];
    if(free[a] || free[b])continue;

    if(rem[u.root(a)] && !u2.same(a,b)){
      u2.connect(a,b);
      ansid.eb(id);
      --rem[u.root(a)];
    }
  }

  cout<<ans<<" "<<siz(ansid)<<ENDL;
  sort(all(ansid));
  for(auto&& i:ansid)cout<<i+1<<" ";
  cout<<ENDL;
}
//! ( . _ . ) !
//CHECK overflow,vector_size,what to output?
//any other simpler approach?
//list all conditions, try mathematical and graphic observation