题解:P4835 [JSOI2014] 学生选课
chenly8128 · · 题解
本题解复杂度为
题目分析
题目要求「使得上同一堂课的学生之间印象最坏的最好」。那么我们根据套路,可以二分答案。
设目前答案为
注意到总共 3 个班,但是除去去年的选择,每人只剩下了两个选择,结合限制条件,于是这就是很经典的 2-sat 问题。
解决该问题可以拆分为 2 步:
- 根据目前的二分结果下的限制条件建有向图。
- tarjan 算法求解强连通分量,验证是否合法。
建图
设目前需要验证的答案为
假设有
显然,两个学生必然拥有至少一个能一起上的课程,如图。
图中,两个学生都能够上课程 2。如果学生 a 选择了课程 2,那么学生 b 就一定不能选择课程 2。对于 b 来说,课程 1 和 课程 2 是对立事件,有且仅有一项成立,所以不选 2 就只能选 1。因此我们可以给 a 的课程 2 选项连一条有向边到 b 的课程 1,象征着 a 选课程 2 能够推出 b 选课程 1。
同理,我们还需要给 b 的课程 2 选项连一条有向边到 a 的课程 0。
通过这样的方法,我们就能完成建图。
tarjan
建完图就是标准的 2-sat 求解,直接对每一个节点跑
如果每一个学生都没有出现这样的情况,那么就是可能的。
代码实现
二分
// Author: chenly8128
// Created: 2025-06-05 21:39:29
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000+10;
int n,x,ne[MAXN][MAXN];
int id[MAXN][3];
int dfn[MAXN<<1],col[MAXN<<1],low[MAXN<<1];
int col_cnt,dfn_cnt;
stack <int> s;
vector <int> g[MAXN<<1];
void init (void) {
memset (dfn,0,sizeof (dfn));
memset (col,0,sizeof (col));
memset (low,0,sizeof (low));
for (int i = 1;i <= 2*n;i++) g[i].clear();
dfn_cnt = col_cnt = 0;
}
void build (int x) {
for (int i = 1;i <= n;i++)
for (int j = x+1;j < n;j++) {
int v = ne[i][j];
for (int t = 0;t < 3;t++)
if (id[i][t] && id[v][t]) {
g[id[i][t]].push_back(id[v][0]+id[v][1]+id[v][2]-id[v][t]);
g[id[v][t]].push_back(id[i][0]+id[i][1]+id[i][2]-id[i][t]);
}
}
}
void tarjan (int x) {
low[x] = dfn[x] = ++dfn_cnt;
s.push(x);
for (int y : g[x]) {
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x],low[y]);
}
else if (!col[y]) low[x] = min(dfn[y],low[x]);
}
if (low[x] == dfn[x]) {
col_cnt++;
while (s.top() != x) {
col[s.top()] = col_cnt;
s.pop();
}
col[x] = col_cnt; s.pop();
}
}
bool check (int x) {
init();
build(x);
for (int i = 1;i <= 2*n;i++)
if (!dfn[i]) tarjan(i);
for (int i = 1;i <= n;i++)
if (col[i] == col[i+n]) return false;
return true;
}
int main (void) {
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1;i <= n;i++) {
cin >> x;
int cur = i;
for (int t = 0;t < 3;t++)
if (x == t) id[i][t] = 0;
else {
id[i][t] = cur;
cur += n;
}
for (int j = 1;j < n;j++) cin >> ne[i][j];
}
int l = 0,r = n-1;
while (l < r) {
int mid = (l + r) >> 1;
if (check (mid)) r = mid;
else l = mid+1;
}
cout << r << '\n';
return 0;
}