「JOISC 2016 Day 4」最差记者 2 题解
前言
形式化题意
建议阅读原题面。
给定长度为
题目分析
首先你必须分析出来,这是一个最大权二分图完美匹配问题,不然连最基础的状压都不会写,然后爆零(当然说的是我自己)。
考虑把
考虑状压 DP。记
以上是我没写出来的部分分。我们考虑直接跑 KM 算法求是
我们猜测一下算法,肯定需要贪心。我们不妨按照某一个顺序枚举右部点,尝试把它通过一条
性质一
对于一个右部点
我们能匹配就让他们匹配上。这么做是对的基于所有点的贡献相同,如果我们按照一个恰当的顺序贪心,如果现在不进行匹配,之后让别的点和
接下来思考,什么时候我们发现他们不能匹配呢?倘若匹配他们后,把它们删去,剩下的图还不能形成一个完美匹配,那我们就不能让他们匹配。
是否存在完美匹配?这是一个很典的问题。
定理一:Hall's marriage theorem(霍尔定理)
wikipedia
Hall's condition is that any subset of vertices from one group has a neighbourhood of equal or greater size.
即,一张二分图是否存在完美匹配的充要条件是,对于左部点 / 右部点的任意子集的邻居点集(有边相连的右部点 / 左部点)大小不小于该子集的大小。不妨将该条件称作 Hall 判定条件。
Necessity Proof
假设二分图
Sufficiency Proof
归纳证明。对于
:
将任意 与任意 匹配,剩下的子图 依然满足命题条件,而 ,根据归纳假设,成立。 :
将 与 组成 组匹配,剩下的子图 依然满足命题条件(倘若 ,则 ,从而有 ,与假设矛盾),故根据归纳假设,成立。
对于任意子集的话……难道我们要枚举子集?显然不现实。这时候,似乎需要用到这个二分图的一些性质了。
性质二
我们发现,对于一个左部点
证明显然,自证不难。
这个性质很自然得到,有什么用呢?
性质三
我们考虑一个右部点的子集
我怎么听不懂你在讲什么?
如果我们把左右部点,分别按照
如此,我们把需要判定的子集个数由
先说结论:按照
我们考虑
至于
#include <cstdio> #include <iostream> #include <stack> using namespace std; const int N = 200010; int n, a[N], b[N], c[N], d[N]; int p[N]; stack<int> st[N]; bool diedL[N], diedR[N]; bool check() { int pcnt = 0; // 后缀右部点有多少 int N_G = 0; // 邻居左部点有多少 for (int i = n, j = n; i >= 1; --i) { if (diedR[i]) continue; ++pcnt; for (; j >= 1 && p[j] >= i; --j) N_G += !diedL[j]; if (N_G < pcnt) return false; } return true; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d%d", &a[i], &b[i]); for (int i = 1; i <= n; ++i) scanf("%d%d", &c[i], &d[i]); for (int i = 1; i <= n; ++i) { for (p[i] = p[i - 1]; p[i] + 1 <= n && d[p[i] + 1] >= b[i]; ++p[i]); } int ans = 0; for (int i = n, j = n; i >= 1; --i) { for (; j >= 1 && p[j] >= i; --j) st[a[j]].push(j); if (st[c[i]].empty()) { ++ans; continue; } int pi_i = st[c[i]].top(); diedL[pi_i] = diedR[i] = true; if (check()) { st[c[i]].pop(); } else { ++ans; diedL[pi_i] = diedR[i] = false; } } printf("%d", ans); return 0; }
那么我们需要优化的部分就是 check
了。
我们考虑将判定条件
发现我们在匹配 check
的时候不考虑
感性理解一下。由于
于是,我们可以使用一棵线段树,方便地执行区间加减操作,查询全局的最小值,看看如果
时间复杂度:
代码
状压
namespace $ya { int f[1 << 17 | 736520]; void tomin(int &a, int b) { if (b < a) a = b; } void solve() { memset(f, 0x3f, sizeof(*f) << n); f[0] = 0; using uint = unsigned; for (uint st = 0; st < 1u << n; ++st) { if (f[st] == 0x3f3f3f3f) continue; int i = __builtin_popcount(st) + 1; for (int j = 1; j <= p[i]; ++j) if (!(st & 1u << (j - 1))) { tomin(f[st | 1u << (j - 1)], f[st] + (a[i] != c[j])); } } printf("%d", f[(1u << n) - 1]); } }
暴力判断合法
#include <cstdio> #include <iostream> #include <stack> using namespace std; const int N = 200010; int n, a[N], b[N], c[N], d[N]; int p[N]; stack<int> st[N]; bool diedL[N], diedR[N]; bool check() { int pcnt = 0; // 后缀右部点有多少 int N_G = 0; // 邻居左部点有多少 for (int i = n, j = n; i >= 1; --i) { if (diedR[i]) continue; ++pcnt; for (; j >= 1 && p[j] >= i; --j) N_G += !diedL[j]; if (N_G < pcnt) return false; } return true; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d%d", &a[i], &b[i]); for (int i = 1; i <= n; ++i) scanf("%d%d", &c[i], &d[i]); for (int i = 1; i <= n; ++i) { for (p[i] = p[i - 1]; p[i] + 1 <= n && d[p[i] + 1] >= b[i]; ++p[i]); } int ans = 0; for (int i = n, j = n; i >= 1; --i) { for (; j >= 1 && p[j] >= i; --j) st[a[j]].push(j); if (st[c[i]].empty()) { ++ans; continue; } int pi_i = st[c[i]].top(); diedL[pi_i] = diedR[i] = true; if (check()) { st[c[i]].pop(); } else { ++ans; diedL[pi_i] = diedR[i] = false; } } printf("%d", ans); return 0; }
数据结构维护
#include <cstdio> #include <iostream> #include <stack> using namespace std; const int N = 200010; int n, a[N], b[N], c[N], d[N]; int p[N]; stack<int> st[N]; #define lson (idx << 1 ) #define rson (idx << 1 | 1) int tag[N << 2], mi[N << 2]; inline void pushtag(int idx, int v) { tag[idx] += v, mi[idx] += v; } inline void pushdown(int idx) { if (!tag[idx]) return; pushtag(lson, tag[idx]); pushtag(rson, tag[idx]); tag[idx] = 0; } void modify(int idx, int trl, int trr, int l, int r, int v) { if (l <= trl && trr <= r) return pushtag(idx, v); pushdown(idx); int mid = (trl + trr) >> 1; if (l <= mid) modify(lson, trl, mid, l, r, v); if (r > mid) modify(rson, mid + 1, trr, l, r, v); mi[idx] = min(mi[lson], mi[rson]); } #undef lson #undef rson int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d%d", &a[i], &b[i]); for (int i = 1; i <= n; ++i) scanf("%d%d", &c[i], &d[i]); for (int i = 1; i <= n; ++i) { for (p[i] = p[i - 1]; p[i] + 1 <= n && d[p[i] + 1] >= b[i]; ++p[i]); } for (int i = 1; i <= n; ++i) { modify(1, 1, n, 1, p[i], 1); modify(1, 1, n, 1, i, -1); } int ans = 0; for (int i = n, j = n; i >= 1; --i) { for (; j >= 1 && p[j] >= i; --j) st[a[j]].push(j); if (st[c[i]].empty()) { ++ans; continue; } int pi_i = st[c[i]].top(); modify(1, 1, n, 1, p[pi_i], -1); modify(1, 1, n, 1, i, 1); if (mi[1] >= 0) { st[c[i]].pop(); } else { ++ans; modify(1, 1, n, 1, p[pi_i], 1); modify(1, 1, n, 1, i, -1); } } printf("%d", ans); return 0; }
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18697527。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】