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;
}
posted @ 2022-04-15 21:07  艾特玖  阅读(50)  评论(0)    收藏  举报