P5838 [USACO19DEC]Milk Visits G
P5838 [USACO19DEC]Milk Visits G
分析
这题有两种写法
我们先说更容易理解,但是时间复杂度较高的写法。
树剖+二分
时间复杂度\(O(Nog^2N)\)
思路非常简单。
我们建立树剖后,将所有颜色的dfs序放入数组,排一下序。
对于每次询问,我们沿链上翻时,该条链的dfs序范围为[id[top[u]],id[u]]
则对于该条链,我们要询问的即为,对应颜色有没有在这个范围内出现过,即为在对应的颜色数组中找到一个编号在重链的dfs序范围内
这个很好求,我们直接进行二分即可。
我们来看看代码
Ac_code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10,M = N*2;
int h[N],e[M],ne[M],w[N],idx;
int sz[N],son[N],fa[N],dep[N];
int top[N],id[N],ts;
vector<int> col[N];
int n,m;
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void dfs1(int u,int pa,int depth)
{
sz[u] = 1,fa[u] = pa,dep[u] = depth;
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==pa) continue;
dfs1(j,u,depth+1);
sz[u] += sz[j];
if(sz[j]>sz[son[u]]) son[u] = j;
}
}
void dfs2(int u,int tp)
{
top[u] = tp,id[u] = ++ts;
if(!son[u]) return ;
dfs2(son[u],tp);
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==fa[u]||j==son[u]) continue;
dfs2(j,j);
}
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++) scanf("%d",w+i);
for(int i=0;i<n-1;i++)
{
int u,v;scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
dfs1(1,-1,1);
dfs2(1,1);
for(int i=1;i<=n;i++) col[w[i]].push_back(id[i]);
for(int i=1;i<=n;i++) sort(col[i].begin(),col[i].end());
for(int i=0;i<m;i++)
{
int u,v,c;scanf("%d%d%d",&u,&v,&c);
bool flag = 0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
auto it = lower_bound(col[c].begin(),col[c].end(),id[top[u]]);
if(it!=col[c].end()&&*it<=id[u])
{
flag = 1;
break;
}
u = fa[top[u]];
}
if(!flag)
{
if(dep[u]<dep[v]) swap(u,v);
auto it = lower_bound(col[c].begin(),col[c].end(),id[v]);
if(it!=col[c].end()&&*it<=id[u]) flag = 1;
}
printf("%d",flag);
}
return 0;
}
思维
时间复杂度\(O(N+M)\)
这个思维的,贼难想。
我们先引入一下大佬的题解。
然后我在说一下,对题解的理解。
我们具体来说说,离线操作中,如何统计出对于两个端点而言,是否路径中有我们想要的颜色的点。
void dfs(int u,int fa)
{
int buf = top[c[u]];
for(int i=0;i<query[u].size();i++)
{
auto id = query[u][i];
if(~ans[id]) ans[id] = (top[q[id]]!=ans[id]);
else ans[id] = top[q[id]];
}
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==fa) continue;
top[c[u]] = j;
dfs(j,u);
}
top[c[u]] = buf;
}
我们以代码为板解释。
这里非常巧妙,扫到每个点的时候,其中top记录的是对于每种颜色而言,它最后出现的编号的下一个点
接下来扫描每个点其所在所有询问
若是这个询问还从未记录过,任何一个所需颜色出现过的点。则进行一次记录,用ans暂存,其中若是在路径中从未出现过,则直接记为0即可。
若是已经被记录过,则将到当前点经过路径中所需颜色出现的最后一次的下一个是哪个。
若两者相同,则说明最后一次出现的所需颜色出现在了从根到LCA的路径中,或者从未出现过
只有不同时,才说明路径中出现过
其中需要提一下的是,为什么要存该颜色的下一个点呢?
主要是为了判断时候不用进行他特判,因为若是LCA处出现了所需颜色,并且我们标记的时候,直接标记的是当前出现的点,则两个端点中记录的位置相同,但是是合法情况。因此我们记录下一个点,这样若不相等则一定是合法解。
我们看看代码。
Ac_code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10,M = N*2;
int h[N],e[M],ne[M],idx;
int c[N],top[N],q[N],ans[N];
vector<int> query[N];
int n,m;
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void dfs(int u,int fa)
{
int buf = top[c[u]];
for(int i=0;i<query[u].size();i++)
{
auto id = query[u][i];
if(~ans[id]) ans[id] = (top[q[id]]!=ans[id]);
else ans[id] = top[q[id]];
}
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==fa) continue;
top[c[u]] = j;
dfs(j,u);
}
top[c[u]] = buf;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++) scanf("%d",c+i);
for(int i=0;i<n-1;i++)
{
int u,v;scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=0;i<m;i++)
{
int u,v,col;
scanf("%d%d%d",&u,&v,&col);
if(c[u]==col||c[v]==col) ans[i] = 1;
else ans[i] = -1;
q[i] = col;
query[u].push_back(i);
query[v].push_back(i);
}
dfs(1,-1);
for(int i=0;i<m;i++) printf("%d",ans[i]);
return 0;
}

浙公网安备 33010602011771号