Description \text{Description} Description
在一张无向图上有 N N N 个点和 M M M 条边,每条边连接着点 a a a 和点 b b b,有无数个人编号为 1 , 2 , … , + ∞ 1,2,\dots,+\infty 1,2,…,+∞。通过某条边是要一定的条件的,对于每条边,有两个值 l l l 和 r r r,表示该条边只允许编号在 [ l , r ] [l,r] [l,r] 区间内的人通过,从 1 1 1 号点到达 N N N 号点,请求出最多有多少人可以通过,并求出这些人的编号(若有多组解取字典序最小的一组 )
- 可能有重边和自环。
- 如果没有人能通过只要输出
Nice work, Dima!
就可以了。 - 对于 30 % 30\% 30% 的数据, 1 ≤ N , M ≤ 10 1\le N,M\le10 1≤N,M≤10;
- 对于 100 % 100\% 100% 的数据, 2 ≤ N ≤ 1000 , 0 ≤ M ≤ 3000 , 1 ≤ a , b ≤ N , 1 ≤ l ≤ r ≤ 1 0 6 2\le N\le1000,0\le M\le3000,1\le a,b\le N,1\le l\le r\le10^6 2≤N≤1000,0≤M≤3000,1≤a,b≤N,1≤l≤r≤106。
Solution \text{Solution} Solution
有两种思路(注意字典序最小的判断):
1. 1. 1. 最长路
遍历每一条边,对于第 i i i 条边,选取边权 ≥ l i \ge l_i ≥li 的边,从 1 1 1 到 N N N 跑最长路,让 r r r 最大,在 l l l 固定的情况下,肯定是 r r r 越大越好。
时间复杂度 O ( M ⋅ M log N ) = O ( M 2 log N ) \mathcal{O}(M\cdot M\log N)=\mathcal{O}(M^2\log N) O(M⋅MlogN)=O(M2logN)。
2. 2. 2. 并查集
同样是遍历每一条边 i i i,将所有满足 l j ≤ l i l_j\le l_i lj≤li 的 j j j,都把 a j a_j aj 和 b j b_j bj 并入一个集合。
那么最终的 l l l 一定是 l i l_i li,所以把所有的 r i r_i ri 取个 min \min min 作为最终的 r r r,当点 1 1 1 和点 N N N 在同一个集合内说明可以尝试去更新答案。
因为是并查集实现,结合路径压缩和按秩合并可以做到 O ( M 2 α ( N ) ) \mathcal{O}(M^2\alpha(N)) O(M2α(N)),不过因为 N ≤ 1000 N\le1000 N≤1000 所以优化不大,主要优化在于常数(
时间复杂度 O ( M 2 α ( N ) ) \mathcal{O}(M^2\alpha(N)) O(M2α(N))。
Code \text{Code} Code
1. 1. 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. 2. 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;
}