#include using namespace std; const int N = 2010, M = 1000010; const int INF = 0x3f3f3f3f; int n, m; int in[N]; int d[N]; int st[N]; // 邻接表 int e[M], h[N], idx, w[M], ne[M]; void add(int a, int b, int c) { e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; } vector path; // 拓扑路径 void topsort() { queue q; /* 考虑极限情况:当每一个站点全部停靠的情况下,相当于左侧集合为空(不停靠的没有) 也就没有左侧集合向虚拟点连边这件事,虚拟点的入度为0,此时,虚拟点需要入队列 */ for (int i = 1; i <= n + m; i++) if (!in[i]) q.push(i); while (q.size()) { int u = q.front(); q.pop(); path.push_back(u); for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; in[j]--; if (in[j] == 0) q.push(j); } } } int main() { // 加快读入 ios::sync_with_stdio(false), cin.tie(0); // 建图 memset(h, -1, sizeof h); cin >> n >> m; // n个火车站,m趟车次 for (int i = 1; i <= m; i++) { memset(st, 0, sizeof st); // 记录哪些是停靠点 int k; cin >> k; // 有多少个依靠站 int S = n, T = 1; for (int j = 1; j <= k; j++) { int x; // 停靠站编号 cin >> x; S = min(S, x); // 第一个停靠点 T = max(T, x); // 最后一个停靠点 st[x] = 1; // 记录哪些是停靠点,哪些不是停靠点 } // 笛卡尔积式建图优化技巧 int u = n + i; // 分配超级源点,虚拟点点号。多条线路,每次一个超级源点,共多建超级源点m个 for (int j = S; j <= T; j++) if (st[j]) // 如果j不是停靠点 add(u, j, 1), in[j]++; // 虚拟点向右侧集合中每个点连一条长度为1的边 else add(j, u, 0), in[u]++; // 左侧集合向 虚拟点u 连一条长度为0的边 } // 求拓扑序 topsort(); /* Q:for (int i = 1; i <= n; i) d[i] = 1; y总这句话为什么不能这样写呀 for (int i = 1; i <= n + m; i) d[i] = 1; A:考虑一种边界情况,当全部站点都停靠的时候,此时虚拟节点是入度为0的节点,虚拟节点的等级d应该为0, 因为连向车站的边权已经是1,否则,如果虚拟节点的等级为1,那么连向的车站等级就会是2。 */ // 递推距离初始化 for (int i = 1; i <= n; i++) d[i] = 1; // n个站点的等级最少是1,而虚拟节点的等级最少是0 // 拓扑路径+递推计算最长路 for (auto u : path) // 枚举拓扑路径的每一个点 for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; d[j] = max(d[j], d[u] + w[i]); // u要求j必须距离自己>=w[i] } int res = 0; for (int i = 1; i <= n; i++) res = max(res, d[i]); // 找出最大值,就是最小等级 printf("%d\n", res); return 0; }