题意
给定一张有 n n n 个节点, m m m 条边的无向图(可能有环或重边),对于每个节点 i i i,有 l i l_i li 和 r i r_i ri,定义在经过该节点后,只能携带于 l i l_i li 和 r i r_i ri 之间的数。
从编号为 1 的节点出发(初始时携带了所有自然数),到达编号为 n n n 的节点,求最后能携带最多的节点。
若节点数相同,输出字典序小的那一组。
若节点数为 0,输出 Nice work, Dima!
。
题解
模拟赛的一道题。
二分 + 图。
二分本质:
在数轴上找到一个临界点,即点的左边都可以满足条件,右边都不可以满足条件(或左边不可以,右边可以)。
在这道题中,r 值是有单调性的。即有一临界点,使得比它小的 r 值无法满足,比它大的 r 值可以满足。
但是,l 不具有单调性。例如,当 l 值为 1 时可以满足,l 值为 5 时可以满足,但是 l 值为 3 时能否满足是无法直接断定的。
所以,r 的值可以通过二分得到,l 的值只能枚举而得。
以上是思路。
但实现的时候我们可以用并查集优化:
我们从小到大枚举 l 的值,在此基础上,我们按 r 的值从大到小枚举每一条边。
保证每一条使用了的边的 e i . l e_i.l ei.l 的值比当前枚举的 l 的值小。
这里就运用了二分的思想。
每用一条边,就用并查集维护,如果 f 1 = f n f_1 = f_n f1=fn,就记录答案,最终输出最佳答案即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define rint register int
const int inf = 1e6 + 5, maxn = 1005;
int n, m;
int cnt, hd[maxn];
struct node{
int s, t;
int l, r;
}e[maxn * 4];
int f[maxn];
int ansl, num;
int recl[maxn * 4];
inline int read ()
{
int x = 1, s = 0;
char ch = getchar ();
while (ch < '0' or ch > '9'){if (ch == '-') x = -1; ch = getchar ();}
while (ch >= '0' and ch <= '9') s = s * 10 + ch - '0', ch = getchar ();
return x * s;
}
inline bool cmp (node a, node b)
{
return a.r == b.r ? a.l < b.l : a.r > b.r;
}
inline void init ()
{
for (rint i (1); i <= n; ++i) f[i] = i;
}
inline int find (int x)
{
return f[x] == x ? x : find (f[x]);
}
int main ()
{
n = read (), m = read ();
for (rint i (1); i <= m; ++i) e[i].s = read (), e[i].t = read (), e[i].l = read (), e[i].r = read (), recl[i] = e[i].l;
sort (e + 1, e + m + 1, cmp);
sort (recl + 1, recl + m + 1);
for (rint i (1); i <= m; ++i)
{
init ();
bool flag = 0;
int tl = recl[i], tr = inf;
for (rint j (1); j <= m; ++j)
{
if (e[j].l > tl) continue;
int u = find (e[j].s), v = find (e[j].t);
if (u != v)
{
f[u] = v;
tr = min (tr, e[j].r);
if (find (1) == find (n))
{
flag = 1;
break;
}
}
}
if (flag and num < tr - tl + 1) ansl = tl, num = tr - tl + 1;
}
if (num == 0)
{
printf ("Nice work, Dima!");
return 0;
}
printf ("%d\n", num);
return 0;
}