[ZJOI2019][洛谷P5327]语言

博客介绍了计算能与点 u 开展贸易活动的城市个数的方法。将所有 s→t 路径组成的连通块记为 G(u),可看成连通所有点 s、t 和 u 的最小生成树。通过开 n 棵线段树维护 G(u),做树上差分,离线 dfs 合并线段树,用欧拉序求 lca,时间复杂度为 O(nlogn)。

Solution

  • s→ts→tst 为包含点 uuu 的一条路径,显然所有的 s→ts→tst 能组成一个连通块(因为路径可以拆成 s→u,u→ts→u,u→tsuut),而这个连通块的边数就是能与 uuu 开展贸易活动的城市个数。

  • 记这个连通块为 G(u)G(u)G(u),显然 G(u)G(u)G(u) 也能看成:连通所有 s,ts,ts,tuuu 的最小生成树

  • 先考虑开 nnn 棵线段树分别维护 G(u)G(u)G(u),对于 s→ts→tst 路径上每个点 uuuG(u)G(u)G(u) 都要加上 s,ts,ts,t 这两个点,线段树以 dfsdfsdfs 序为下标,节点 xxx 维护以下信息 (对应区间 [l,r][l,r][l,r]):

    f(x):f(x):f(x): G(u)G(u)G(u)dfsdfsdfs 序在 [l,r][l,r][l,r] 中的点(记这些点的集合为 H(x)H(x)H(x)),加上根节点,组成的连通块的边数
    s(x):s(x):s(x): HHHdfsdfsdfs 序最小的点
    t(x):t(x):t(x): HHHdfsdfsdfs 序最大的点

  • xxx 的左右儿子分别为 x2,x3x2,x3x2,x3,则一般情况下

    f(x)=f(x2)+f(x3)−deep(lca(t(x2),t(x3))f(x)=f(x2)+f(x3)-deep(lca(t(x2), t(x3))f(x)=f(x2)+f(x3)deep(lca(t(x2),t(x3))
    s(x)=s(x2)s(x)=s(x2)s(x)=s(x2)
    t(x)=t(x3)t(x)=t(x3)t(x)=t(x3)

    当然还有一些 H(x2)H(x2)H(x2)H(x3)H(x3)H(x3) 为空集的情况需要特判

  • 具体实现中,可以把 s→ts→tst 做树上差分,即拆成:在 s,ts,ts,t 处出现次数 +1+1+1 ,lca(s,t),fa(lca(s,t))lca(s,t),fa(lca(s,t))lca(s,t),fa(lca(s,t)) 处出现次数 −1-11 (上述出现次数均为s,ts,ts,t 的出现次数),因此,线段树的叶子节点还要记录每个点的出现次数

  • 然后离线下来dfsdfsdfs 整棵树一遍,在回溯的时候,把儿子的线段树合并到该点,并执行位于该点的 +1,−1+1,-1+1,1 修改

  • rt[u]rt[u]rt[u]uuu 对应线段树的根,G(u)G(u)G(u) 的边数即 f(rt[u])−deep(lca(s(rt[u]),t(rt[u])))f(rt[u])-deep(lca(s(rt[u]),t(rt[u])))f(rt[u])deep(lca(s(rt[u]),t(rt[u])))

  • 注意没有限制 u&lt;vu&lt;vu<v,即答案要除以 222

  • 使用欧拉序求 lcalcalca 即可做到 O(nlogn)O(nlogn)O(nlogn) 的时间复杂度

Code

#include <bits/stdc++.h>

using namespace std;

#define pb push_back
#define ll long long

template <class t>
inline void read(t & res)
{
   char ch;
   while (ch = getchar(), !isdigit(ch));
   res = ch ^ 48;
   while (ch = getchar(), isdigit(ch))
   res = res * 10 + (ch ^ 48); 
}

const int e = 2e5 + 5;
vector<int>g[e], h[e];
ll ans;
int n, m, rt[e], logn[e], st[e][18], dep[e], nxt[e * 2], go[e * 2], num, adj[e], fa[e];
int dfn1[e], dfn2[e], pool;
struct node
{
   int l, r, cnt, f, s, t;
}c[e * 30];

inline void add(int x, int y)
{
   nxt[++num] = adj[x];
   adj[x] = num;
   go[num] = y;
   nxt[++num] = adj[y];
   adj[y] = num;
   go[num] = x;
} 

inline void dfs1(int u, int pa)
{
   dep[u] = dep[pa] + 1;
   dfn1[u] = ++dfn1[0];
   dfn2[u] = ++dfn2[0];
   st[dfn1[0]][0] = u;
   fa[u] = pa;
   for (int i = adj[u]; i; i = nxt[i])
   {
       int v = go[i];
       if (v == pa) continue;
       dfs1(v, u);
       st[++dfn1[0]][0] = u;
   }
}

inline int lca(int x, int y)
{
   if (!x || !y) return 0;
   if (dfn1[x] > dfn1[y]) swap(x, y);
   int l = dfn1[x], r = dfn1[y], k = logn[r - l + 1], u = st[l][k], 
       v = st[r - (1 << k) + 1][k];
   return dep[u] < dep[v] ? u : v;
}

inline void collect(int x)
{
   int l = c[x].l, r = c[x].r;
   c[l].s ? c[x].s = c[l].s : c[x].s = c[r].s;
   c[r].t ? c[x].t = c[r].t : c[x].t = c[l].t;
   c[x].f = c[l].f + c[r].f - dep[lca(c[l].t, c[r].s)];
}

inline int merge(int x, int y, int l, int r)
{
   if (!x || !y) return x ^ y;
   if (l == r)
   {
       c[x].cnt += c[y].cnt; 
       c[x].f |= c[y].f;
       c[x].s |= c[y].s;
       c[x].t |= c[y].t;
       return x;
   }
   int mid = l + r >> 1;
   c[x].l = merge(c[x].l, c[y].l, l, mid);
   c[x].r = merge(c[x].r, c[y].r, mid + 1, r);
   collect(x); 
   return x;
}

inline void insert(int &x, int l, int r, int pos, int v)
{
   if (!x) x = ++pool;
   if (l == r)
   {
       c[x].cnt += v;
       if (!c[x].cnt) c[x].f = c[x].s = c[x].t = 0;
       else c[x].f = dep[pos], c[x].s = c[x].t = pos;
       return;
   } 
   int mid = l + r >> 1;
   if (dfn2[pos] <= mid) insert(c[x].l, l, mid, pos, v);
   else insert(c[x].r, mid + 1, r, pos, v);
   collect(x);
}

inline void init()
{
   int i, j;
   logn[0] = -1;
   for (i = 1; i <= dfn1[0]; i++) logn[i] = logn[i >> 1] + 1;
   for (j = 1; (1 << j) <= dfn1[0]; j++)
   for (i = 1; i + (1 << j) - 1 <= dfn1[0]; i++)
   {
       int u = st[i][j - 1], v = st[i + (1 << j - 1)][j - 1];
       st[i][j] = (dep[u] < dep[v] ? u : v); 
   }
}

inline void dfs2(int u, int pa)
{
   for (int i = adj[u]; i; i = nxt[i])
   {
       int v = go[i];
       if (v == pa) continue;
       dfs2(v, u);
       rt[u] = merge(rt[u], rt[v], 1, n);
   }
   for (auto v : g[u]) insert(rt[u], 1, n, v, 1);
   for (auto v : h[u]) insert(rt[u], 1, n, v, -1);
   ans += c[rt[u]].f - dep[lca(c[rt[u]].s, c[rt[u]].t)];
}

int main()
{
   int i, x, y;
   read(n); read(m);
   for (i = 1; i < n; i++) read(x), read(y), add(x, y);
   dfs1(1, 0);
   init();
   while (m--)
   {
       read(x); read(y); 
       int z = lca(x, y);
       g[x].pb(x); g[x].pb(y); g[y].pb(x); g[y].pb(y);
       h[z].pb(x); h[z].pb(y); h[fa[z]].pb(x); h[fa[z]].pb(y);
   }
   dfs2(1, 0);
   cout << ans / 2 << endl;
   return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值