cf366 D. Dima and Trap Graph(枚举、二分、dfs、并查集)
题意:
一张图,每条边有属性 \([l,r]\)。找一条从点1到点n的路径,使路径上所有边的区间的交集最大。
\(n\le 1e3, m\le 3e3,1\le l_i\le r_r\le 1e6\)
思路:
法一:枚举答案的区间左端点,二分找右端点,dfs判断(300ms)
如果在 \(l,r\) 的整个值域 \(1e6\) 上枚举和二分,复杂度是 \(O(m10^6log(10^6))\),会超时。注意到答案区间的左端点一定是某条边的区间左端点,右端点也一定是出现过的某右端点,故只需考虑所有出现过的 \(l/r\) 。复杂度 \(O(mmlogm)\)
const int N = 3e3 + 5;
int n, m, Ls[N], Rs[N];
struct node { int v, l, r; };
vector<node> G[N];
bool vis[N];
bool dfs(int u, int L, int R)
{
if(L > R) return 0;
if(u == n) return 1;
vis[u] = 1;
for(auto &[v,l,r] : G[u])
if(!vis[v] && l <= L && r >= R)
if(dfs(v, L, R)) return 1;
return 0;
}
signed main()
{
iofast;
cin >> n >> m;
for(int i = 1; i <= m; i++)
{
int a, b, l, r; cin >> a >> b >> l >> r;
G[a].pb({b,l,r}), G[b].pb({a,l,r});
Ls[i] = l, Rs[i] = r;
}
sort(Ls+1, Ls+1+m), sort(Rs+1, Rs+1+m);
int ans = 0;
for(int i = 1; i <= m; i++)
{
int l = 0, r = m;
while(l < r)
{
int mid = l + r + 1 >> 1;
memset(vis, 0, sizeof vis);
if(dfs(1, Ls[i], Rs[mid])) l = mid; else r = mid - 1;
}
ans = max(ans, Rs[l]-Ls[i]+1);
}
if(ans) cout << ans;
else cout << "Nice work, Dima!";
}
法二:dfs+剪枝(124ms)
剪枝1:如果当前区间长度小于答案就不继续搜了
剪枝2:记录上次搜这个节点的区间 \([lastL,lastR]\),如果现在要搜上次区间的子区间就退出。注意这剪枝是很随便的,只考虑上次的
复杂度玄学,跑得飞快
bool vis[N];
int lastL[N], lastR[N];
void dfs(int u, int L, int R)
{
if(R-L+1 <= ans) return; //剪枝1
if(L >= lastL[u] && R <= lastR[u]) return; //剪枝2
lastL[u] = L, lastR[u] = R;
if(u == n)
{
ans = max(ans, R-L+1);
return;
}
for(auto &[v,l,r] : G[u]) if(!vis[v])
vis[v] = 1,
dfs(v, max(l, L), min(r, R)),
vis[v] = 0;
}
signed main()
{
iofast;
cin >> n >> m;
for(int i = 1; i <= m; i++)
{
int a, b, l, r; cin >> a >> b >> l >> r;
G[a].pb({b,l,r}), G[b].pb({a,l,r});
}
vis[1] = 1;
dfs(1, 1, 1e6);
if(ans) cout << ans;
else cout << "Nice work, Dima!";
}
法三:并查集(93ms)
对所有边按左端点排序。如果答案右端点是 \(r\) ,那么所有左端点小于等于 \(r\),右端点大于等于 \(r\) 的边都能选。把这些边的起点终点合并,看能不能使点1和点n连通。
int n, m, ans;
struct node { int u, v, l, r; };
bool cmp(node a, node b) {return a.l < b.l; }
vector<node> edges;
signed main()
{
iofast;
cin >> n >> m;
while(m--)
{
int a, b, l, r; cin >> a >> b >> l >> r;
edges.pb({a,b,l,r}); //只需存边
}
sort(all(edges), cmp);
//枚举答案右端点
for(auto [u1,v1,l1,r1] : edges) //其实咋样枚举都无所谓
{
for(int i = 1; i <= n; i++) p[i] = i; //每次初始化
for(auto [u2,v2,l2,r2] : edges)
{
if(l2 > r1) break; //没交集
if(r2 < r1) continue; //不以r1为答案右端点
mer(u2, v2);
if(get(1) == get(n))
ans = max(ans, r1 - l2 + 1);
}
}
if(ans) cout << ans;
else cout << "Nice work, Dima!";
}

浙公网安备 33010602011771号