// code template is in https://github.com/drken1215/algorithm/blob/master/template_minimum.cpp #pragma GCC optimize("Ofast") #pragma GCC optimize("unroll-loops") #include using namespace std; //------------------------------// // Utility //------------------------------// using ll = long long; using i128 = __int128_t; using u128 = __uint128_t; using pint = pair; using pll = pair; using tll = array; using fll = array; using vint = vector; using vll = vector; using dint = deque; using dll = deque; using vvint = vector>; using vvll = vector>; using vpll = vector>; template using min_priority_queue = priority_queue, greater>; template inline bool chmax(S &a, T b) { return (a < b ? a = b, 1 : 0); } template inline bool chmin(S &a, T b) { return (a > b ? a = b, 1 : 0); } template inline auto maxll(S a, T b) { return max(ll(a), ll(b)); } template inline auto minll(S a, T b) { return min(ll(a), ll(b)); } template auto max(const T &a) { return *max_element(a.begin(), a.end()); } template auto min(const T &a) { return *min_element(a.begin(), a.end()); } template auto argmax(const T &a) { return max_element(a.begin(), a.end()) - a.begin(); } template auto argmin(const T &a) { return min_element(a.begin(), a.end()) - a.begin(); } template auto accum(const vector &a) { return accumulate(a.begin(), a.end(), T()); } template auto accum(const deque &a) { return accumulate(a.begin(), a.end(), T()); } #define REP(i, a) for (long long i = 0; i < (long long)(a); i++) #define REP2(i, a, b) for (long long i = a; i < (long long)(b); i++) #define RREP(i, a) for (long long i = (a)-1; i >= (long long)(0); --i) #define RREP2(i, a, b) for (long long i = (b)-1; i >= (long long)(a); --i) #define EB emplace_back #define PF push_front #define PB push_back #define MP make_pair #define FI first #define SE second #define ALL(x) x.begin(), x.end() #define COUT(x) cout << #x << " = " << (x) << " (L" << __LINE__ << ")" << endl // input template istream& operator >> (istream &is, vector &P) { for (int i = 0; i < (int)P.size(); ++i) cin >> P[i]; return is; } template istream& operator >> (istream &is, deque &P) { for (int i = 0; i < (int)P.size(); ++i) cin >> P[i]; return is; } template istream& operator >> (istream &is, vector> &P) { for (int i = 0; i < (int)P.size(); ++i) cin >> P[i]; return is; } // output template ostream& operator << (ostream &s, const pair &P) { return s << '<' << P.first << ", " << P.second << '>'; } template ostream& operator << (ostream &s, const array &P) { return s << '<' << P[0] << "," << P[1] << '>'; } template ostream& operator << (ostream &s, const array &P) { return s << '<' << P[0] << "," << P[1] << "," << P[2] << '>'; } template ostream& operator << (ostream &s, const array &P) { return s << '<' << P[0] << "," << P[1] << "," << P[2] << "," << P[3] << '>'; } template ostream& operator << (ostream &s, const vector &P) { for (int i = 0; i < P.size(); ++i) { if (i > 0) { s << " "; } s << P[i]; } return s; } template ostream& operator << (ostream &s, const deque &P) { for (int i = 0; i < P.size(); ++i) { if (i > 0) { s << " "; } s << P[i]; } return s; } template ostream& operator << (ostream &s, const vector> &P) { for (int i = 0; i < P.size(); ++i) { s << endl << P[i]; } return s << endl; } template ostream& operator << (ostream &s, const set &P) { for (auto it : P) { s << "<" << it << "> "; } return s; } template ostream& operator << (ostream &s, const multiset &P) { for (auto it : P) { s << "<" << it << "> "; } return s; } template ostream& operator << (ostream &s, const unordered_set &P) { for (auto it : P) { s << "<" << it << "> "; } return s; } template ostream& operator << (ostream &s, const map &P) { for (auto it : P) { s << "<" << it.first << "->" << it.second << "> "; } return s; } template ostream& operator << (ostream &s, const unordered_map &P) { for (auto it : P) { s << "<" << it.first << "->" << it.second << "> "; } return s; } void yes(bool a) { cout << (a ? "yes" : "no") << endl; } void YES(bool a) { cout << (a ? "YES" : "NO") << endl; } void Yes(bool a) { cout << (a ? "Yes" : "No") << endl; } const vector DX = {1, 0, -1, 0, 1, -1, 1, -1}; const vector DY = {0, 1, 0, -1, 1, -1, -1, 1}; // edge class (for min-cost flow) template struct FlowCostEdge { // core members int rev, from, to; FLOW cap, icap, flow; COST cost; // constructor constexpr FlowCostEdge() noexcept = default; constexpr FlowCostEdge(int rev, int from, int to, FLOW cap, COST cost) : rev(rev), from(from), to(to), cap(cap), icap(cap), flow(0), cost(cost) { } constexpr FlowCostEdge(int rev, int from, int to, FLOW cap, FLOW rcap, COST cost) : rev(rev), from(from), to(to), cap(cap), icap(cap), flow(rcap), cost(cost) { } void reset() { flow -= icap - cap; cap = icap; } // debug friend ostream& operator << (ostream& s, const FlowCostEdge& e) { return s << e.from << " -> " << e.to << " (" << e.cap << ", " << e.flow << ", " << e.cost << ")"; } }; // graph class (for min-cost flow) template struct FlowCostGraph { // core members vector>> list; vector> pos; // pos[i] := {vertex, order of list[vertex]} of i-th edge vector pot; // pot[v] := potential (e.cost + pot[e.from] - pos[e.to] >= 0) bool include_negative_edge = false; // constructor FlowCostGraph(int n = 0) : list(n), pot(n), include_negative_edge(false) { } void init(int n = 0) { list.clear(), list.resize(n); pos.clear(); pot.assign(n, 0); include_negative_edge = false; } // getter vector> &operator [] (int i) { assert(0 <= i && i < (int)list.size()); return list[i]; } const vector> &operator [] (int i) const { assert(0 <= i && i < (int)list.size()); return list[i]; } size_t size() const noexcept { return list.size(); } FlowCostEdge &get_rev_edge(const FlowCostEdge &e) { return list[e.to][e.rev]; } const FlowCostEdge &get_rev_edge(const FlowCostEdge &e) const { return list[e.to][e.rev]; } FlowCostEdge &get_edge(int i) { return list[pos[i].first][pos[i].second]; } const FlowCostEdge &get_edge(int i) const { return list[pos[i].first][pos[i].second]; } vector> get_edges() const { vector> edges; for (int i = 0; i < (int)pos.size(); ++i) { edges.push_back(get_edge(i)); } return edges; } // change edges void reset() { for (int i = 0; i < (int)list.size(); ++i) { for (FlowCostEdge &e : list[i]) e.reset(); } } // add_edge void add_edge(int from, int to, FLOW cap, COST cost) { assert(0 <= from && from < (int)list.size() && 0 <= to && to < (int)list.size()); assert(cap >= 0); int from_id = (int)list[from].size(), to_id = (int)list[to].size(); if (from == to) to_id++; pos.emplace_back(from, from_id); list[from].push_back(FlowCostEdge(to_id, from, to, cap, 0, cost)); list[to].push_back(FlowCostEdge(from_id, to, from, 0, cap, -cost)); if (cost < 0) include_negative_edge = true; } void add_edge(int from, int to, FLOW cap, FLOW rcap, COST cost) { assert(0 <= from && from < (int)list.size() && 0 <= to && to < (int)list.size()); assert(cap >= 0); int from_id = (int)list[from].size(), to_id = (int)list[to].size(); if (from == to) to_id++; pos.emplace_back(from, from_id); list[from].push_back(FlowCostEdge(to_id, from, to, cap, rcap, cost)); list[to].push_back(FlowCostEdge(from_id, to, from, rcap, cap, -cost)); if (cost < 0) include_negative_edge = true; } void add_bidirected_edge(int from, int to, FLOW cap, COST cost) { assert(0 <= from && from < (int)list.size() && 0 <= to && to < (int)list.size()); assert(cap >= 0); add_edge(from, to, cap, cap, cost); } // find initial potential (to resolve initial negative-edge) // pot[v] := potential (e.cost + pot[e.from] - pos[e.to] >= 0) bool calc_potential_dag() { pot.assign(size(), 0); vector deg(size(), 0), st; for (int v = 0; v < (int)size(); v++) for (const auto &e : list[v]) deg[e.to] += (e.cap > 0); st.reserve(size()); for (int v = 0; v < (int)size(); v++) if (!deg[v]) st.emplace_back(v); for (int i = 0; i < (int)size(); i++) { if ((int)st.size() == i) return false; // not DAG int cur = st[i]; for (const auto &e : list[cur]) { if (e.cap <= 0) continue; deg[e.to]--; if (deg[e.to] == 0) st.emplace_back(e.to); if (pot[e.to] >= pot[cur] + e.cost) pot[e.to] = pot[cur] + e.cost; } } return true; } bool calc_potential_spfa() { pot.assign(size(), 0); queue que; vector inque(size(), false); vector cnt(size(), 0); for (int v = 0; v < (int)size(); v++) que.push(v), inque[v] = true; while (!que.empty()) { int cur = que.front(); que.pop(); inque[cur] = false; if (cnt[cur] > (int)size()) return false; // include negative-cycle cnt[cur]++; for (const auto &e : list[cur]) { if (e.cap <= 0) continue; if (pot[e.to] > pot[cur] + e.cost) { pot[e.to] = pot[cur] + e.cost; if (!inque[e.to]) inque[e.to] = true, que.push(e.to); } } } return true; } bool calc_potential() { return calc_potential_dag() || calc_potential_spfa(); } bool init_potential() { if (!include_negative_edge) return true; return calc_potential(); } // decompose flow into s-t simple paths and cycles using Path = vector>; pair, vector> decompose(int s, int t) const { struct Arc { int to; FLOW rem; int eidx; }; vector> fg(list.size()); for (int v = 0; v < (int)list.size(); v++) { for (int j = 0; j < (int)list[v].size(); j++) { FLOW f = list[v][j].icap - list[v][j].cap; if (f > 0) fg[v].push_back({list[v][j].to, f, j}); } } vector paths, cycles; auto build = [&](const vector> &route, bool is_cycle) { FLOW mi = numeric_limits::max(); for (auto [v,i] : route) mi = min(mi, fg[v][i].rem); vector> seq; for (auto [v,i] : route) { fg[v][i].rem -= mi; FlowCostEdge e = list[v][fg[v][i].eidx]; e.flow = mi; seq.push_back(e); } if (is_cycle) cycles.push_back(std::move(seq)); else paths.push_back(std::move(seq)); }; // Phase 1: extract all cycles and make graph DAG const int NOTSEEN = 0, INSTACK = 1, FINISH = 2; vector color(list.size(), NOTSEEN); vector pos_in_stack(list.size(), -1); vector> stk; auto dfs = [&](auto &&dfs, int v) -> bool { color[v] = INSTACK; pos_in_stack[v] = (int)stk.size(); for (int i = 0; i < (int)fg[v].size(); i++) { if (fg[v][i].rem <= 0) continue; int u = fg[v][i].to; if (color[u] == INSTACK) { vector> route; for (int k = pos_in_stack[u]; k < (int)stk.size(); k++) { route.push_back(stk[k]); } route.push_back({v, i}); build(route, true); return true; } if (color[u] == NOTSEEN) { stk.push_back({v, i}); if (dfs(dfs, u)) return true; stk.pop_back(); } } color[v] = FINISH; pos_in_stack[v] = -1; return false; }; while (true) { fill(color.begin(), color.end(), NOTSEEN); stk.clear(); bool found = false; for (int v = 0; v < (int)list.size() && !found; v++) { if (color[v] == NOTSEEN && dfs(dfs, v)) found = true; } if (!found) break; } // Phase 2: find all s-t paths vector ptr(list.size(), 0); auto next_arc = [&](int v) -> int { while (ptr[v] < (int)fg[v].size() && fg[v][ptr[v]].rem <= 0) ptr[v]++; return ptr[v] < (int)fg[v].size() ? ptr[v] : -1; }; while (next_arc(s) != -1) { vector> route; int v = s; while (v != t) { int i = next_arc(v); route.push_back({v, i}); v = fg[v][i].to; } build(route, false); } return {paths, cycles}; } // debug friend ostream& operator << (ostream& s, const FlowCostGraph &G) { const auto &edges = G.get_edges(); for (const auto &e : edges) s << e << endl; return s; } }; // min-cost max-flow (<= limit_flow), slope ver. template vector> MinCostFlowSlope(FlowCostGraph &G, int S, int T, FLOW limit_flow) { // result values FLOW cur_flow = 0; COST cur_cost = 0, pre_cost = numeric_limits::max() / 2; vector> res; res.emplace_back(cur_flow, cur_cost); // intermediate values vector dist((int)G.size(), numeric_limits::max() / 2); vector prevv((int)G.size(), -1), preve((int)G.size(), -1); // dual auto dual_step = [&]() -> bool { dist.assign((int)G.size(), numeric_limits::max() / 2); dist[S] = 0; priority_queue, vector>, greater>> que; que.emplace(0, S); while (!que.empty()) { auto [cur, v] = que.top(); que.pop(); if (cur > dist[v]) continue; for (int i = 0; i < (int)G[v].size(); i++) { const auto &e = G[v][i]; COST add = e.cost + G.pot[v] - G.pot[e.to]; if (e.cap > 0 && dist[e.to] > dist[v] + add) { dist[e.to] = dist[v] + add; prevv[e.to] = v; preve[e.to] = i; que.emplace(dist[e.to], e.to); } } } return dist[T] < numeric_limits::max() / 2; }; // primal auto primal_step = [&]() -> void { for (int v = 0; v < (int)G.size(); v++) { if (dist[v] < numeric_limits::max() / 2) G.pot[v] += dist[v]; else G.pot[v] = numeric_limits::max() / 2; } FLOW flow = limit_flow - cur_flow; COST cost = G.pot[T] - G.pot[S]; for (int v = T; v != S; v = prevv[v]) { flow = min(flow, G[prevv[v]][preve[v]].cap); } for (int v = T; v != S; v = prevv[v]) { FlowCostEdge &e = G[prevv[v]][preve[v]]; FlowCostEdge &re = G.get_rev_edge(e); e.cap -= flow, e.flow += flow; re.cap += flow, re.flow -= flow; } cur_flow += flow; cur_cost += flow * cost; if (pre_cost == cost) res.pop_back(); res.emplace_back(cur_flow, cur_cost); pre_cost = cost; }; // initialize potential assert(G.init_potential()); // primal-dual while (cur_flow < limit_flow) { if (!dual_step()) break; primal_step(); } return res; } // min-cost max-flow, slope ver. template vector> MinCostFlowSlope(FlowCostGraph &G, int S, int T) { return MinCostFlowSlope(G, S, T, numeric_limits::max()); } // min-cost max-flow (<= limit_flow) template pair MinCostFlow(FlowCostGraph &G, int S, int T, FLOW limit_flow) { return MinCostFlowSlope(G, S, T, limit_flow).back(); } // min-cost max-flow (<= limit_flow) template pair MinCostFlow(FlowCostGraph &G, int S, int T) { return MinCostFlow(G, S, T, numeric_limits::max()); } //------------------------------// // Solver //------------------------------// int main() { ll N, A, C; cin >> N >> A; vll B(A); cin >> B; cin >> C; vll D(C); cin >> D; sort(ALL(B), greater()); sort(ALL(D)); ll s = N * 2, t = s + 1; FlowCostGraph G(t + 1); REP(i, N) G.add_edge(s, i, 1, 0), G.add_edge(i+N, t, 1, 0); REP(i, N) REP(j, N) { ll aq = i / A, ar = i % A, aleft = aq * A, aright = (aq + 1) * A; ll cq = j / C, cr = j % C, cleft = cq * C, cright = (cq + 1) * C; if (max(aleft, cleft) < min(aright, cright)) { G.add_edge(i, j+N, 1, (B[ar] > D[cr] ? 0 : 1)); } } //COUT(G); auto [maxflow, mincost] = MinCostFlow(G, s, t, N); auto res = N - mincost; //COUT(maxflow); COUT(mincost); COUT(G); cout << res << endl; }