P5663 加工零件 题解
简要题意:
给定一个图,每次询问从 \(x\) 节点开始,\(y\) 步能不能达到 \(1\) 号节点。
算法一
这也是我本人考场算法。就是 深搜 。
因为你会发现,如果 \(x\) 用 \(y \% 2\) 步能到 \(1\) 节点,那肯定 \(y\) 步能到。
原因是:剩下的 \(y - y \% 2\) 是偶数,只要重复走一条边多次即可。
我们用 \(f_{i,0/1}\) 表示,从 \(i\) 号节点经过 偶数步(0) 奇数步(1) 能到 \(1\) 号节点的最短步数。
从 \(1\) 开始深搜即可。如果已有答案当前答案更优,则结束。
时间复杂度:\(O(n^2)\).(代码后会解释)
实际得分:\(80pts\).(考场上也就这么多)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int f[N][2];
int n,m,q;
vector<int>G[N];
inline void dfs(int dep,int bs) {
if(f[dep][bs%2]!=-1 && f[dep][bs%2]<=bs) return; //无需更新答案
f[dep][bs%2]=bs; //记录答案
for(int i=0;i<G[dep].size();i++) {
int x=G[dep][i];
dfs(G[dep][i],bs+1);
}
}
int main(){
n=read(),m=read(),q=read();
memset(f,-1,sizeof(f));
while(m--) {
int x=read(),y=read();
G[x].push_back(y);
G[y].push_back(x);
} f[1][0]=2; dfs(1,0);
while(q--) {
int x=read(),y=read();
if(f[x][y%2]!=-1 && f[x][y%2]<=y) puts("Yes");
else puts("No");
} //判断
return 0;
}
你会说,这个程序的时间复杂度是 \(O(n)\) 吧?怎么是 \(O(n^2)\)?
这个问题也困扰了我很久,以至于很难想清楚,怎样的一种情况可以卡掉它。
重写了代码之后就发现了。
因为,如果是个完全图的话,每个点都会被其它点到达(尽管被 \(\texttt{return}\) 但还是做了),所以是 \(O(n^2)\).
你会说,那也不会是完全图啊?\(n\) 和 \(m\) 是同阶啊?
也对。所以完全图不用考虑,给定图肯定是稀疏图。
那么,问题在哪儿?
你想,如果原图是一个长 \(10^5\) 的一个环。
此时,这个程序极有可能炸掉。
因为,如果你的遍历顺序一路走完了这个环,然后又走一遍(因为奇偶性不同,导致走 \(2\) 次)。
接着,里面的每个点的探索次数为 \(2\),也就是说每个点又开始向其它点探索。
但是,假设你在第三次走环的时候 \(1\) 结束掉,此时 \(n\) 号节点就返到 \(n-1\) 号节点。
然后 \(n-1\) 号节点的递归次数就变成 \(2\),它把锅都甩给了两边,又到了 \(n\),然后才结束。
每个点都把自己身上的锅(就是递归次数)扔给相邻的,但是,你必须要走 \(n\) 次(就到头) 才可以确定一个节点的答案。
此时 \(O(n^2)\).
那么,如何优化这个程序呢?
算法二
深搜不行,用宽搜。
用了宽搜就不存在 “互相甩锅” 这个问题。每个人只会甩常数次锅。
时间复杂度:\(O(n+m)\).(\(n\) 有系数,但被忽略)
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int f[N][2];
int n,m,q;
vector<int>G[N];
inline void bfs() {
queue<pair<int,int> >q;
memset(f,0x3f,sizeof(f));
for(int i=0;i<G[1].size();i++) {
f[G[1][i]][1]=1; //与1相邻的点,奇数步能到达1
q.push(make_pair(G[1][i],1)); //入队
} while(!q.empty()) {
int x=q.front().first,y=q.front().second;
q.pop();
for(int i=0;i<G[x].size();i++) {
int k=G[x][i];
if(y+1<f[k][1-y%2]) {
f[k][1-y%2]=y+1;
q.push(make_pair(k,y+1)); //入队
} //1-y%2,就是 (y+1)%2,改变奇偶性后 0->1,1->0,正好用 1 减掉。
}
}
}
int main(){
n=read(),m=read(),q=read();
while(m--) {
int x=read(),y=read();
G[x].push_back(y);
G[y].push_back(x);
} bfs();
while(q--) {
int x=read(),y=read();
if(f[x][y%2]<=y) puts("Yes");
else puts("No");
}
return 0;
}
后记
这个题目说明几点:
-
宽搜不一定旁边要开哈希。
-
深搜有的时候难以处理极大环。