提问者:小点点

树状查询及其操作


给出了一个有N个节点的树。 每个节点都有一个颜色配置项。 我们还被给予N-1个查询,并且在每个查询中,我们被告知要破坏一个以前未破坏的边。 每次我们破坏一条边时,我们必须报告断开连接的节点对的数量。 这里,两个节点i和j被称为断开的,如果在破坏之前,你可以使用尚未破坏的边从i到达j,并且如果ci=cj。

如何处理这个问题? 我的一些测试用例正在使用dfs运行,但有些显示超出了时间限制。 请帮帮忙

我的代码:

 #include<bits/stdc++.h>

 using namespace std;
  #define ll long long int
  int par[300002];
  i nt sz[300002];
  vector<pair<ll, ll>> arr;
   ll n, i, a, b, x, y;

   ll find(ll x)
   {
   if (x != par[x])
  {
    par[x] = find(par[x]);
    }
return par[x];
  }

    bool same(ll x, ll y)
   {
   return find(x) == find(y);
    }

   void merge(ll x, ll y)
   {
 ll x1 = find(x);
 ll y1 = find(y);

if (sz[x1] > sz[y1])
{
    par[y] = x;
    sz[x1] += sz[y1];
}
else
{
    par[x] = y;
    sz[y1] += sz[x1];
}
   }

    int main()
   {

cin >> n;
vector<ll> ans(n + 1);

for (i = 1; i <= n; i++)
{
    par[i] = i;
    sz[i] = 1;
}

for (i = 1; i <= n - 1; i++)
{
    cin >> a >> b;
    arr.push_back(make_pair(a, b));
}

int c[n + 1];
for (i = 1; i <= n; i++)
{
    cin >> c[i];
}

ans[n] = (n * (n - 1)) / 2;

for (i = n - 1; i >= 0; i--)
{
    x = arr[i].first;
    y = arr[i].second;

    if (same(x, y))
    {
        ans[i] = ans[i + 1];
        continue;
    }

    ll s1 = sz[find(x)];
    ll s2 = sz[find(y)];
    merge(x, y);

    ans[i] = max(ans[i + 1] - (s1 * s2), 0LL);
}
int z;

for (i = 1; i <= n - 1; i++)
{
    cin >> z;
    cout << ans[z] << '\n';
}

   }

我已经接受了一些测试案例,但是大部分案例都超过了时间限制。我想了很多关于如何优化的问题,但是无法做到。请帮助我


共1个答案

匿名用户

这是那些可以更容易地反过来解决的问题之一:

  • 我们的树在所有查询之后会是什么样子? 仅限所有断开连接的节点
  • 现在,对于每个查询(从最后一个开始),我们可以连接节点uv,如果它们共享相同的颜色
  • 这可以使用dsu(又称union find)数据结构来完成,对于每个节点u我们保存节点的父节点和包含该节点的集合的大小(最初的父节点为-1或null,大小为1)
  • 然后连接2个节点,我们可以通过乘以uv所属的树的大小来检查有多少路径会被破坏

总复杂度:O(n*logn)*

(*假设DSU只使用1个优化(路径压缩/大小压缩),如果同时使用这两个优化,则速度会更快,如果不使用O(n^2))