【题解】CF366D
\(\text{Description}\)
在一张无向图上有 \(N\) 个点和 \(M\) 条边,每条边连接着点 \(a\) 和点 \(b\),有无数个人编号为 \(1,2,\dots,+\infty\)。通过某条边是要一定的条件的,对于每条边,有两个值 \(l\) 和 \(r\),表示该条边只允许编号在 \([l,r]\) 区间内的人通过,从 \(1\) 号点到达 \(N\) 号点,请求出最多有多少人可以通过,并求出这些人的编号(若有多组解取字典序最小的一组 )
- 可能有重边和自环。
- 如果没有人能通过只要输出
Nice work, Dima!
就可以了。 - 对于 \(30\%\) 的数据,\(1\le N,M\le10\);
- 对于 \(100\%\) 的数据,\(2\le N\le1000,0\le M\le3000,1\le a,b\le N,1\le l\le r\le10^6\)。
\(\text{Solution}\)
有两种思路(注意字典序最小的判断):
\(1.\) 最长路
遍历每一条边,对于第 \(i\) 条边,选取边权 \(\ge l_i\) 的边,从 \(1\) 到 \(N\) 跑最长路,让 \(r\) 最大,在 \(l\) 固定的情况下,肯定是 \(r\) 越大越好。
时间复杂度 \(\mathcal{O}(M\cdot M\log N)=\mathcal{O}(M^2\log N)\)。
\(2.\) 并查集
同样是遍历每一条边 \(i\),将所有满足 \(l_j\le l_i\) 的 \(j\),都把 \(a_j\) 和 \(b_j\) 并入一个集合。
那么最终的 \(l\) 一定是 \(l_i\),所以把所有的 \(r_i\) 取个 \(\min\) 作为最终的 \(r\),当点 \(1\) 和点 \(N\) 在同一个集合内说明可以尝试去更新答案。
因为是并查集实现,结合路径压缩和按秩合并可以做到 \(\mathcal{O}(M^2\alpha(N))\),不过因为 \(N\le1000\) 所以优化不大,主要优化在于常数(
时间复杂度 \(\mathcal{O}(M^2\alpha(N))\)。
\(\text{Code}\)
\(1.\)
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int MAXN = 1005;
const int MAXM = 3005;
const int INF = 0x3f3f3f3f;
int cnt;
int head[MAXN];
struct edge
{
int to, l, r, nxt;
}e[MAXM << 1];
void add(int u, int v, int l, int r)
{
e[++cnt] = edge{v, l, r, head[u]};
head[u] = cnt;
}
int dis[MAXN];
bool vis[MAXN];
struct node
{
int u, r;
bool operator <(const node &x)const
{
if (x.r == r)
{
return x.u < u;
}
return x.r > r;
}
};
void init()
{
memset(dis, -INF, sizeof(dis));
memset(vis, false, sizeof(vis));
}
void dijkstra(int l)
{
init();
dis[1] = INF;
priority_queue<node> pq;
pq.push(node{1, dis[1]});
while (!pq.empty())
{
int u = pq.top().u;
pq.pop();
if (vis[u])
{
continue;
}
vis[u] = true;
for (int i = head[u]; i; i = e[i].nxt)
{
if (e[i].l > l) //取在区间内的
{
continue;
}
int v = e[i].to;
if (dis[v] < min(dis[u], e[i].r)) //r要取min
{
dis[v] = min(dis[u], e[i].r);
pq.push(node{v, dis[v]});
}
}
}
}
int l[MAXM], r[MAXM];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int u, v;
scanf("%d%d%d%d", &u, &v, l + i, r + i);
add(u, v, l[i], r[i]);
add(v, u, l[i], r[i]);
}
int ansk = 0, ansl = 0, ansr = 0;
for (int i = 1; i <= m; i++)
{
dijkstra(l[i]);
int res = dis[n] - l[i] + 1;
if (ansk < res || (ansk == res && ansl > l[i]))
{
ansk = res;
ansl = l[i];
ansr = dis[n];
}
}
if (ansk)
{
printf("%d\n", ansk);
for (int i = ansl; i <= ansr; i++)
{
printf("%d ", i);
}
}
else
{
puts("Nice work, Dima!");
}
return 0;
}
\(2.\)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 1005;
const int MAXM = 3005;
const int INF = 0x3f3f3f3f;
struct edge
{
int u, v, l, r;
}e[MAXM];
bool cmp(edge x, edge y)
{
if (x.r == y.r)
{
return x.l < y.l;
}
return x.r > y.r;
}
int n, m, r, ansk, ansl, ansr;
int fa[MAXN], siz[MAXN];
void init()
{
for (int i = 1; i <= n; i++)
{
fa[i] = i;
siz[i] = 1;
}
}
int find(int x)
{
if (x == fa[x])
{
return x;
}
return fa[x] = find(fa[x]); //路径压缩
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d%d", &e[i].u, &e[i].v, &e[i].l, &e[i].r);
}
sort(e + 1, e + m + 1, cmp);
for (int i = 1; i <= m; i++)
{
init();
r = INF;
for (int j = 1; j <= m; j++)
{
if (e[j].l <= e[i].l)
{
int x = find(e[j].u), y = find(e[j].v);
if (x != y)
{
if (siz[x] > siz[y]) //按秩合并
{
fa[y] = x;
siz[x] += siz[y];
}
else
{
fa[x] = y;
siz[y] += siz[x];
}
r = min(r, e[j].r);
if (find(1) == find(n))
{
break;
}
}
}
}
if (find(1) == find(n) && (ansk < r - e[i].l + 1 || (ansk == r - e[i].l + 1 && ansl > e[i].l)))
{
ansk = r - e[i].l + 1;
ansl = e[i].l;
ansr = r;
}
}
if (ansk)
{
printf("%d\n", ansk);
for (int i = ansl; i <= ansr; i++)
{
printf("%d ", i);
}
}
else
{
puts("0");
}
return 0;
}