#include<bits/stdc++.h>
using namespace std;
using ll = long long;

/*
算法思想:
这是一个带「必选边」的最小生成树问题,基于Kruskal算法改进
核心逻辑:
1.  题目要求必须包含所有 p=1 的必选边,哪怕它们形成环
2.  在此基础上,用 p=0 的可选边(按权值从小到大)补全生成树,保证总代价最小
3.  最终需要确保所有城市(顶点)都连通,否则输出 -1
适用场景:稀疏图(V与E数量接近)
*/

// 定义边的结构体:存储每条边的原始编号、端点、权值、必选标记、选中标记
struct Edge {
    ll i; // 边的原始输入编号(输出时需要用到)
    ll u, v; // 边的两个端点(城市编号)
    ll w; // 修建这条道路的花费(权值)
    ll p; // 必选标记:1表示必选,0表示可选
    bool r = false; // 标记这条边是否被选中加入最终方案
};

// 全局变量定义
ll n, m;        // n:城市数量(顶点数),m:修路计划数量(边数)
ll cnt = 0;     // 最终选中的边的总数

const ll MAXN = 1e5 + 5; // 最大顶点数,根据题目规模调整
ll parent[MAXN];       // 并查集父节点数组:维护顶点的连通性
ll rank_[MAXN];        // 并查集秩数组:用于按秩合并,优化并查集效率

// 并查集初始化:每个顶点初始时自成一个连通分量
// 算法前置步骤:为连通性检查准备基础结构
void init(ll n) {
    for (ll i = 1; i <= n; ++i) { // 城市编号通常从1开始
        parent[i] = i; // 每个顶点的父节点初始化为自己
        rank_[i] = 0;  // 初始秩为0,表示树的高度为0
    }
}

// 并查集查找操作(带路径压缩)
// 功能:快速找到顶点x所在连通分量的根节点,路径压缩可将后续查询复杂度降为近似O(1)
ll find(ll x) {
    if (parent[x] != x) {
        // 路径压缩:将x直接指向根节点,减少后续查找的层数
        parent[x] = find(parent[x]);
    }
    return parent[x];
}

// 并查集合并操作(按秩合并)
// 功能:将两个不同的连通分量合并,按秩合并保持树的平衡,避免退化成链表
void union_(ll x, ll y) {
    ll root_x = find(x); // 找到x的根节点
    ll root_y = find(y); // 找到y的根节点
    if (root_x == root_y) return; // 已在同一集合,无需合并

    // 秩小的树合并到秩大的树下,保持树的平衡
    if (rank_[root_x] < rank_[root_y]) {
        parent[root_x] = root_y;
    } else {
        parent[root_y] = root_x;
        // 若秩相等,合并后根节点的秩+1(树的高度增加1)
        if (rank_[root_x] == rank_[root_y]) {
            rank_[root_x]++;
        }
    }
}

// Kruskal算法主函数:求解带必选边的最小生成树
void kruskal(ll n, vector<Edge>& edges) {
    // --------------------------
    // 算法步骤1:对所有边排序
    // 排序规则:
    // 1. 优先处理必选边(p=1 排在 p=0 前面)
    // 2. 同优先级下,按权值w升序(保证可选边选代价最小的,必选边也优先选代价小的)
    // --------------------------
    sort(edges.begin(), edges.end(), [](Edge a, Edge b) {
        if (a.p != b.p) return a.p > b.p; // 第一关键字:p降序
        else return a.w < b.w;            // 第二关键字:w升序
    });

    // --------------------------
    // 算法步骤2:初始化并查集
    // --------------------------
    init(n);

    // 哈希表:记录每个顶点是否被覆盖(用于最终检查所有城市是否连通)
    unordered_set<ll> s;

    // --------------------------
    // 算法步骤3:遍历排序后的边,贪心选择符合条件的边
    // --------------------------
    for (ll i = 0; i < m; i++) {
        ll u = edges[i].u, v = edges[i].v, w = edges[i].w, p = edges[i].p;

        // 情况1:当前边的两个端点不在同一连通分量
        // → 选这条边不会形成环,合并连通分量并标记为选中
        if (find(u) != find(v)) {
            union_(u, v);         // 合并两个连通分量
            s.insert(u);
            s.insert(v);
            cnt++;                // 选中边数+1
            edges[i].r = true;    // 标记该边被选中
        }
        // 情况2:当前边是必选边(p=1),即使两个端点已连通(形成环)也必须选中
        // → 满足题目“必选计划必须包含”的要求
        else if (p == 1) {
            edges[i].r = true;    // 标记该边被选中
            cnt++;                // 选中边数+1
        }
    }

    // --------------------------
    // 算法步骤4:检查所有城市是否都连通
    // --------------------------
    for (ll i = 1; i <= n; i++) {
        if (s.find(i) == s.end()) { // 存在城市未被任何边覆盖 → 无法连通
            cout << -1;
            return;
        }
    }

    // --------------------------
    // 算法步骤5:输出结果
    // --------------------------
    cout << cnt << "\n";
    for (ll i = 0; i < m; i++) {
        if (edges[i].r == true) {
            cout << edges[i].i << " "; // 输出选中边的原始编号
        }
    }
}

int main() {
    // 输入处理:读取城市数和计划数,然后读取每条边的信息
    cin >> n >> m;
    vector<Edge> edges(m);
    for (ll i = 0; i < m; i++) {
        edges[i].i = i + 1; // 边的编号从1开始(和输入顺序一致)
        cin >> edges[i].u >> edges[i].v >> edges[i].w >> edges[i].p;
    }

    // 执行带必选边的Kruskal算法
    kruskal(n, edges);

    return 0;
}