/** author: shobonvip created: 2025.11.17 14:16:14 **/ #include using namespace std; typedef long long ll; #define rep(i, s, n) for (int i = (int)(s); i < (int)(n); i++) #define rrep(i, s, n) for (int i = (int)(n)-1; i >= (int)(s); i--) #define all(v) v.begin(), v.end() template bool chmin(T &a, const T &b) { if (a <= b) return false; a = b; return true; } template bool chmax(T &a, const T &b) { if (a >= b) return false; a = b; return true; } template T max(vector &a){ assert(!a.empty()); T ret = a[0]; for (int i=0; i<(int)a.size(); i++) chmax(ret, a[i]); return ret; } template T min(vector &a){ assert(!a.empty()); T ret = a[0]; for (int i=0; i<(int)a.size(); i++) chmin(ret, a[i]); return ret; } template T sum(vector &a){ T ret = 0; for (int i=0; i<(int)a.size(); i++) ret += a[i]; return ret; } struct scc_graph { int n, k; vector> g, rg; vector vs, cmp, seen; void dfs(int v) { seen[v] = 1; for(int u : g[v]) { if(seen[u] < 0) dfs(u); } vs.emplace_back(v); } void rdfs(int v) { cmp[v] = k; for(int u : rg[v]) { if(cmp[u] < 0) rdfs(u); } } scc_graph(int _n) : n(_n), g(_n), rg(_n) {} void add_edge(int f, int t) { g[f].emplace_back(t); rg[t].emplace_back(f); } vector> scc() { seen.assign(n, -1); rep(i, 0, n) { if(seen[i] < 0) dfs(i); } reverse(all(vs)); cmp.assign(n, -1); k = 0; for(int v : vs) { if(cmp[v] < 0) { rdfs(v); k++; } } vector> ret(k); rep(i, 0, n) { ret[cmp[i]].emplace_back(i); } return ret; } }; // all hash: cb5cee template struct mf_graph { struct nedge { int to, rev; Cap cap; }; int nn; vector> pos; vector> g; mf_graph() : nn(0) {} explicit mf_graph(int n) : nn(n), g(n) {} int add_edge(int from, int to, Cap cap) { int m = pos.size(); pos.push_back({from, int(g[from].size())}); int frid = int(g[from].size()); int toid = int(g[to].size()); if(from == to) toid++; g[from].push_back(nedge{to, toid, cap}); g[to].push_back(nedge{from, frid, 0}); return m; } Cap flow(int s, int t) { return flow(s, t, numeric_limits::max()); } Cap flow(int s, int t, Cap flow_limit) { vector lv(nn), iter(nn); queue q; auto bfs = [&]() { fill(all(lv), -1); lv[s] = 0; queue().swap(q); q.push(s); while(!q.empty()) { int v = q.front(); q.pop(); for(auto e : g[v]) { if(e.cap == 0 || lv[e.to] >= 0) continue; lv[e.to] = lv[v] + 1; if(e.to == t) return; q.push(e.to); } } }; auto dfs = [&](auto self, int v, Cap up) { if(v == s) return up; Cap res = 0; int lvvv = lv[v]; for(int& i = iter[v]; i < int(g[v].size()); i++) { nedge& e = g[v][i]; if(lvvv <= lv[e.to] || g[e.to][e.rev].cap == 0) continue; Cap d = self(self, e.to, min(up - res, g[e.to][e.rev].cap)); if(d <= 0) continue; g[v][i].cap += d; g[e.to][e.rev].cap -= d; res += d; if(res == up) return res; } lv[v] = nn; return res; }; Cap flow = 0; while(flow < flow_limit) { bfs(); if(lv[t] == -1) break; fill(all(iter), 0); Cap f = dfs(dfs, t, flow_limit - flow); if(!f) break; flow += f; } return flow; } /* struct edge { int from, to; Cap cap, flow; }; edge get_edge(int i) { int m = int(pos.size()); auto _e = g[pos[i].first][pos[i].second]; auto _re = g[_e.to][_e.rev]; return edge{pos[i].first, _e.to, _e.cap + _re.cap, _re.cap}; } vector min_cut(int s) { vector visited(nn); queue q; q.push(s); while(!q.empty()) { int p = q.front(); q.pop(); visited[p] = true; for(auto e : g[p]) { if(e.cap && !visited[e.to]) { visited[e.to] = true; q.push(e.to); } } } return visited; } vector edges() { int m = int(pos.size()); vector result; for(int i = 0; i < m; i++) { result.push_back(get_edge(i)); } return result; } */ }; vector,vector>> dm_decomposition(int L, int R, vector> edges) { for (auto [x, y]: edges) { assert(0 <= x && x < L); assert(0 <= y && y < R); } mf_graph mf(L+R+2); for (auto [x, y]: edges) { mf.add_edge(x, y+L, 1); } rep(i,0,L) mf.add_edge(L+R, i, 1); rep(i,0,R) mf.add_edge(i+L, L+R+1, 1); mf.flow(L+R, L+R+1); vector match(L+R, -1); rep(x,0,L) { for (auto &e: mf.g[x]) { if (L <= e.to && e.to < L+R && e.cap == 0) { match[x] = e.to; match[e.to] = x; } } } scc_graph scc(L+R); for (auto [x,y]: edges) { scc.add_edge(x, y+L); } rep(i,0,L) { if (match[i] >= L) scc.add_edge(match[i], i); } auto ns = scc.scc(); vector cmp_map(ns.size(), -2); vector vis(L+R); vector st; rep(c,0,2) { vector> to(L+R); auto color = [&L](int x) { return x >= L; }; for (auto [u, v]: edges) { v += L; if (color(u) != c) swap(u, v); to[u].push_back(v); if(match[u] == v) to[v].push_back(u); } rep(i,0,L+R) { if (match[i]>=0 || color(i) != c || vis[i]) continue; vis[i] = 1; st = {i}; while(!st.empty()) { int now = st.back(); cmp_map[scc.cmp[now]] = c-1; st.pop_back(); for (int nxt: to[now]) { if (!vis[nxt]) { vis[nxt] = 1; st.push_back(nxt); } } } } } int nset = 1; rep(n,0,(int)ns.size()) { if (cmp_map[n] == -2) cmp_map[n] = nset++; } for (auto &x: cmp_map) { if (x==-1) x = nset; } nset++; vector,vector>> groups(nset); rep(i,0,L){ if (match[i]<0) continue; int c = cmp_map[scc.cmp[i]]; groups[c].first.push_back(i); groups[c].second.push_back(match[i]-L); } rep(i,0,L) { if (match[i]>=0) continue; int c = cmp_map[scc.cmp[i]]; groups[c].first.push_back(i); } rep(i,0,R) { if (match[L+i]>=0) continue; int c = cmp_map[scc.cmp[L+i]]; groups[c].second.push_back(i); } return groups; } // https://hitonanode.github.io/cplib-cpp/graph/test/dulmage_mendelsohn.yuki1615.test.cpp std::vector, std::vector>> verify_dulmage_mendelsohn(int L, int R, const std::vector> &edges) { auto ret = dm_decomposition(L, R, edges); assert(ret.size() >= 2); vector lord(L, -1), rord(R, -1); set> edges_set(edges.begin(), edges.end()); for (int igrp = 0; igrp < int(ret.size()); ++igrp) { for (int vl : ret[igrp].first) { assert(lord[vl] < 0); lord[vl] = igrp; } for (int vr : ret[igrp].second) { assert(rord[vr] < 0); rord[vr] = igrp; } if (igrp == 0) { assert(ret[igrp].first.size() < ret[igrp].second.size() or ret[igrp].first.empty()); } else if (igrp + 1 == int(ret.size())) { assert(ret[igrp].first.size() > ret[igrp].second.size() or ret[igrp].second.empty()); } else { assert(ret[igrp].first.size() == ret[igrp].second.size()); assert(ret[igrp].first.size()); } for (int j = 0; j < min(ret[igrp].first.size(), ret[igrp].second.size()); ++j) { auto u = ret[igrp].first[j], v = ret[igrp].second[j]; assert(edges_set.count(make_pair(u, v))); } } assert(count(lord.begin(), lord.end(), -1) == 0); assert(count(rord.begin(), rord.end(), -1) == 0); for (auto e : edges) { assert(0 <= e.first and e.first < L); assert(0 <= e.second and e.second < R); assert(lord.at(e.first) <= rord.at(e.second)); // Check topological order } return ret; } int main(){ ios_base::sync_with_stdio(false); cin.tie(NULL); int N, M, K, L; cin >> N >> M >> K >> L; vector>> z2xy(K + 1); while (L--) { int x, y, z; cin >> x >> y >> z; x--, y--; z2xy[K - z].emplace_back(x, y); } vector vtp(N + M, 3); vector> alive_edges; long long ret = 0; int nmatch = 0; vector experience12(N + M); vector fixed_pair(N, -1); for (const auto &xys : z2xy) { for (auto p : xys) { int u = p.first, v = p.second; if (experience12[u] or experience12[N + v]) continue; alive_edges.emplace_back(u, v); } auto dm_ret = verify_dulmage_mendelsohn(N, M, alive_edges); int nmatchnxt = 0; for (const auto &p : dm_ret) nmatchnxt += min(p.first.size(), p.second.size()); ret = ret * 2 + nmatchnxt - nmatch; for (auto l : dm_ret.front().first) vtp[l] = 2, experience12[l] = 1; for (auto r : dm_ret.front().second) vtp[r + N] = 3; for (auto l : dm_ret.back().first) vtp[l] = 3; for (auto r : dm_ret.back().second) vtp[r + N] = 2, experience12[r + N] = 1; for (int i = 1; i + 1 < int(dm_ret.size()); ++i) { for (int j = 0; j < int(dm_ret[i].first.size()); ++j) { int l = dm_ret[i].first[j], r = dm_ret[i].second[j]; if (fixed_pair[l] < 0) { vtp[l] = vtp[r + N] = 1, fixed_pair[l] = r; experience12[l] = experience12[r + N] = 1; alive_edges.emplace_back(l, r); } } } for (int cur = 0; cur < int(alive_edges.size());) { int u = alive_edges[cur].first, v = alive_edges[cur].second; if (vtp[u] + vtp[v + N] == 5 or fixed_pair[u] == v) { cur++; } else { alive_edges[cur].swap(alive_edges.back()); alive_edges.pop_back(); } } nmatch = nmatchnxt; } cout << ret << endl; }