HS_fu3的语录 题解
题目:HS_fu3的语录
目录
这个题可以用 LCT 在线用更优的时间复杂度 (\(O(n\log{n})\) )通过本题,但是本蒟蒻不会。
请大家阅读。
先放题解代码(码风比较独特,请见谅):点击查看代码
#include<bits/stdc++.h>
#define ent putchar('\n')
#define con putchar(' ')
#define int long long
#define lid (id << 1)
#define rid (id << 1 | 1)
#define pushup(id) tr[id].sum = tr[lid].sum + tr[rid].sum
#define add(u,v) to[++ tot] = v,nxt[tot] = h[u],h[u] = tot
#define Blue_Archive return 0
using namespace std;
const int N = 2e5 + 3;
const int M = 4e5 + 3;
int n;
int m;
int rt;
int tot;
int cnt;
int cntq;
int h[N];
int a[N];
int f[N];
int ff[N];
int fa[N];
int to[M];
int rk[N];
int nxt[M];
int siz[N];
int dep[N];
int dfn[N];
int top[N];
int son[N];
struct miku
{
int l,r,sum;
}tr[N * 4];
struct mika
{
int op;
int x;
int y;
int tx;
int ty;
}q[N];
struct youka
{
int val;
int rt;
}ans[N];
inline int read()
{
int k = 0,f = 1;
char c = getchar();
while(c < '0' || c > '9')
{
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - '0',c = getchar();
return k * f;
}
inline void write(int x)
{
if(x < 0) putchar('-'),x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
inline int find(int x){return ff[x] == x ? x : ff[x] = find(ff[x]);}
inline void build(int id,int l,int r)
{
tr[id].l = l;
tr[id].r = r;
if(l == r)
{
tr[id].sum = a[rk[l]];
return;
}
int mid = (l + r) >> 1;
build(lid,l,mid);
build(rid,mid + 1,r);
pushup(id);
}
inline void dfs1(int u,int f)
{
fa[u] = f;
dep[u] = dep[f] + 1;
siz[u] = 1;
int maxson = -1;
for(int i = h[u],v;i;i = nxt[i])
{
v = to[i];
if(v == f) continue;
dfs1(v,u);
siz[u] += siz[v];
if(siz[v] > maxson) maxson = siz[v],son[u] = v;
}
}
inline void dfs2(int u,int tp)
{
dfn[u] = ++ cnt;
rk[cnt] = u;
top[u] = tp;
if(!son[u]) return;
dfs2(son[u],tp);
for(int i = h[u],v;i;i = nxt[i])
{
v = to[i];
if(v == fa[u] || v == son[u]) continue;
dfs2(v,v);
}
}
inline void update(int id,int pos,int val)
{
if(tr[id].l == tr[id].r)
{
tr[id].sum = val;
return;
}
int mid = (tr[id].l + tr[id].r) >> 1;
if(mid >= pos) update(lid, pos, val);
else update(rid, pos, val);
pushup(id);
}
inline int query(int id,int l,int r)
{
if(tr[id].l > r || tr[id].r < l) return 0;
if(l <= tr[id].l && tr[id].r <= r) return tr[id].sum;
return query(lid,l,r) + query(rid,l,r);
}
inline int query_path(int x,int y)
{
int res = 0;
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
res += query(1,dfn[top[x]],dfn[x]);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x,y);
res += query(1,dfn[x],dfn[y]);
return res;
}
signed main()
{
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
n = read();
m = read();
for(int i = 1;i <= n;i ++)
{
a[i] = read();
f[i] = i;
ff[i] = i;
}
char op;
for(int i = 1;i <= m;i ++)
{
cin >> op;
if(op == 'm')
{
q[i].op = 1;
q[i].x = read();
q[i].y = read();
q[i].tx = find(q[i].x);
q[i].ty = find(q[i].y);
if(q[i].tx == q[i].ty) continue;
f[q[i].ty] = q[i].tx;
ff[q[i].ty] = q[i].tx;
}
if(op == 'f')
{
q[i].op = 2;
q[i].x = read();
q[i].tx = find(q[i].x);
}
if(op == 'c')
{
q[i].op = 3;
q[i].x = read();
q[i].y = read();
}
}
rt = n + 1;
for(int i = 1;i <= n;i ++)
{
if(i == f[i])
{
add(i,rt);
add(rt,i);
}
else
{
add(i,f[i]);
add(f[i],i);
}
}
dfs1(rt,0);
dfs2(rt,rt);
build(1,1,n + 1);
for(int i = 1;i <= m;i ++)
{
if(q[i].op == 1)//meg
{
ans[++ cntq].val = query_path(q[i].x,q[i].tx);
ans[cntq].val += query_path(q[i].y,q[i].ty);
}
if(q[i].op == 2)//query
{
ans[++ cntq].val = query_path(q[i].x,q[i].tx);
ans[cntq].rt = q[i].tx;
}
if(q[i].op == 3)//change
{
update(1,dfn[q[i].x],q[i].y);
}
}
for(int i = 1;i <= cntq;i ++)
{
if(ans[i].rt)
{
write(ans[i].rt);con;write(ans[i].val);ent;
}
else
{
write(ans[i].val);ent;
}
}
Blue_Archive;
}
正文开始
读题发现,这个题就是求该节点到该节点所在连通块的根节点的路径点权和
然后动态维护这个森林。进行并查集操作。
善良的出题人(在下)给了注意:数据不保证最终一定是一棵树。(赛时因为没有想到狂调 1 hour)
所以我们可以从这些点的最终形态入手。
首先我们要查询最短路径长,所以考虑树剖。
那么既然要树剖,总得有棵树啊。
然后就可以自然而然地想到最终形态。
既然最后是森林(显然可得)。
如果对每棵树进行剖分的话,时间复杂度直接爆炸。
于是我们充分发挥人类智慧,将每棵树的根连一个超级根。
把森林变成树。
然后我们就可以愉快地树剖了。
那么观察操作 \(merge\),发现这棵树与另一棵树合并后,这两棵树的形态均未改变,在这棵树上的 \(find\) 值,与在合并之后的树上的 \(find\) 值是一样的。
于是我们就可以记录此时该节点所处树的根。
然后再把最后的树建出来。
在最后的树上跑最短路径。
对于点权:通过树剖我们已经把整个序列的DFS序搞出来了。
之后直接用一棵线段树维护区间和就可以了。
时间复杂度分析:
树剖:预处理:\(O(n)\),每次查询: \(O(\log{n})\)。
线段树:查询:\(O(\log{n})\),修改:\(O(\log{n})\)。
总时间复杂度:\(O(n\log^2{n})\)
可以通过此题,跑的飞快!
好了,感谢观看本文,感谢看或做了本题,谢谢大家!
与你的日常,便是奇迹

浙公网安备 33010602011771号