第五场春训,谢谢jsh和wjy学长 qwq!
(树剖终于练熟了)
(这绝对是史上最长的一篇blog… )
【BUAA Spring Training 05】
Tags:DFS序 莫队 LCA 询问分块 ZKW线段树 + 树链剖分 树链剖分
Problem A. 向下取整,越除越小
[A] 题意
给定一棵树,结点数为 n ≤ 2 ∗ 1 0 5 n\le 2*10^5 n≤2∗105,编号从 1 \text{1} 1 开始,接下来有 m ≤ 2 ∗ 1 0 5 m\le 2*10^5 m≤2∗105 个操作:
- ① u v d u\ \ v\ \ d u v d,表示拿着 d d d 这个数然后从 u u u 出发沿最短路走向 v v v,每经过一条边(记其边权为 w w w)就让 d = ⌊ d / w ⌋ d = \lfloor d/w \rfloor d=⌊d/w⌋。然后输出走完之后得到的数。
- ② i e i\ \ e i e,表示把第 i i i 条边(边是按输入顺序编号的,从 1 1 1 编到 n − 1 n-1 n−1)的边权改成 e e e,保证严格改小。此操作没有输出。
边权的范围是 [ 1 , 1 0 18 ] [1, 10^{18}] [1,1018]。
[A] 思路
解法1
直接拿 树剖 搞行不行呢?这不就是个单点修改 + 区间积查询吗?
这里可不能想当然… 因为这里的操作是整除,逐个整除和一次性整除的结果一定就相等吗?
还是需要证明一下的。
考虑用数学归纳法证明:
∀
x
,
a
i
∈
Z
∗
(
i
∈
Z
∗
)
\forall\ x, a_i\in\mathbb{Z}^*(i\in\mathbb{Z}^*)
∀ x,ai∈Z∗(i∈Z∗),记
P
n
=
∏
i
=
1
n
a
i
P_n=\prod\limits_{i=1}^{n}a_{i}
Pn=i=1∏nai。现证明
∀
n
∈
Z
∗
\forall\ n\in \mathbb{Z} ^*
∀ n∈Z∗,
⌊
x
P
n
⌋
\lfloor \frac{x}{P_{n}} \rfloor
⌊Pnx⌋
=
=
=
⌊
⌊
⌊
⌊
x
a
1
⌋
a
2
⌋
.
.
.
⌋
a
n
⌋
\lfloor \frac{\lfloor \frac{\lfloor \frac{\lfloor \frac{x}{a_1} \rfloor}{a_2}\rfloor}{...} \rfloor}{a_n} \rfloor
⌊an⌊...⌊a2⌊a1x⌋⌋⌋⌋ 成立。
-
①: n = 1 n=1 n=1 时,显然上式成立。
-
②: n = 2 n=2 n=2 时,证明上式成立:(为方便表示,记 b = a 1 , c = a 2 b=a_1, c=a_2 b=a1,c=a2,即证明 ⌊ x b ∗ c ⌋ \lfloor \frac{x}{b*c} \rfloor ⌊b∗cx⌋ = = = ⌊ ⌊ x b ⌋ c ⌋ \lfloor \frac{\lfloor \frac{x}{b} \rfloor}{c}\rfloor ⌊c⌊bx⌋⌋ 成立)
证明:记 x = d b ∗ b + r b , d b = d c ∗ c + r c x = d_b*b + r_b,\ \ \ d_b = d_c*c + r_c x=db∗b+rb, db=dc∗c+rc,则显然有 ⌊ ⌊ x b ⌋ c ⌋ \lfloor \frac{\lfloor \frac{x}{b} \rfloor}{c}\rfloor ⌊c⌊bx⌋⌋ = d c = d_c =dc 。
此时 x = b ∗ c ∗ d c + b ∗ r c + r b x=b*c*d_c+b*r_c+r_b x=b∗c∗dc+b∗rc+rb
∴ \therefore ∴ x b c \frac{x}{bc} bcx = d c =d_c =dc + + + r c c \frac{r_c}{c} crc + + + r b b ∗ c \frac{r_b}{b*c} b∗crb故欲证 ⌊ x b ∗ c ⌋ \lfloor \frac{x}{b*c} \rfloor ⌊b∗cx⌋ = = = ⌊ ⌊ x b ⌋ c ⌋ \lfloor \frac{\lfloor \frac{x}{b} \rfloor}{c}\rfloor ⌊c⌊bx⌋⌋ = d c =d_c =dc,只需证 { \{ { r c c \frac{r_c}{c} crc + + + r b b ∗ c \frac{r_b}{b*c} b∗crb } ∈ [ 0 , 1 ) \}\in[0, 1) }∈[0,1) 。
又 ∵ \because ∵ r c c \frac{r_c}{c} crc ∈ [ 0 , \in[0, ∈[0, c − 1 c \frac{c-1}{c} cc−1 ] ] ], r b b ∗ c \frac{r_b}{b*c} b∗crb ∈ [ 0 , \in[0, ∈[0, b − 1 b ∗ c \frac{b-1}{b*c} b∗cb−1 ] ] ]
∴ { \therefore\ \{ ∴ { r c c \frac{r_c}{c} crc + + + r b b ∗ c \frac{r_b}{b*c} b∗crb } ∈ [ 0 , \}\in[0, }∈[0, b ∗ c − b + b − 1 b ∗ c \frac{b*c-b\ +\ b-1}{b*c} b∗cb∗c−b + b−1 ] ] ],即 { \{ { r c c \frac{r_c}{c} crc + + + r b b ∗ c \frac{r_b}{b*c} b∗crb } ∈ [ 0 , 1 − \}\in[0,\ 1- }∈[0, 1− 1 b ∗ c \frac{1}{b*c} b∗c1 ] ⊂ [ 0 , 1 ) ]\subset [0, 1) ]⊂[0,1)
得证。
-
③: ∀ k ≥ 2 \forall\ k \ge 2 ∀ k≥2,证明上式在 n = k n=k n=k 时成立可推在 n = k + 1 n=k+1 n=k+1 时亦成立:
证明:由于 n = k n=k n=k 时上式成立,则有 ⌊ x P k ⌋ \lfloor \frac{x}{P_{k}} \rfloor ⌊Pkx⌋ = = = ⌊ ⌊ ⌊ ⌊ x a 1 ⌋ a 2 ⌋ . . . ⌋ a k ⌋ \lfloor \frac{\lfloor \frac{\lfloor \frac{\lfloor \frac{x}{a_1} \rfloor}{a_2}\rfloor}{...} \rfloor}{a_k} \rfloor ⌊ak⌊...⌊a2⌊a1x⌋⌋⌋⌋ ---- ( ∗ ) (*) (∗)
∵ x , P k , a k + 1 ∈ Z ∗ \because x,\ P_{k},\ a_{k+1}\in \mathbb{Z}^* ∵x, Pk, ak+1∈Z∗
∴ \therefore ∴ 由 ② 知 ⌊ x P k ∗ a k + 1 ⌋ \lfloor \frac{x}{P_{k}*a_{k+1}} \rfloor ⌊Pk∗ak+1x⌋ = = = ⌊ ⌊ x P k ⌋ a k + 1 ⌋ \lfloor \frac{\lfloor \frac{x}{P_{k}} \rfloor}{a_{k+1}}\rfloor ⌊ak+1⌊Pkx⌋⌋
∴ \therefore ∴ 由 ( ∗ ) (*) (∗) 知 ⌊ x P k ∗ a k + 1 ⌋ \lfloor \frac{x}{P_{k}*a_{k+1}} \rfloor ⌊Pk∗ak+1x⌋ = = = ⌊ ⌊ ⌊ ⌊ ⌊ x a 1 ⌋ a 2 ⌋ . . . ⌋ a k ⌋ a k + 1 ⌋ \lfloor \frac{\lfloor \frac{\lfloor \frac{\lfloor \frac{\lfloor \frac{x}{a_1} \rfloor}{a_2}\rfloor}{...} \rfloor}{a_k} \rfloor}{a_{k+1}}\rfloor ⌊ak+1⌊ak⌊...⌊a2⌊a1x⌋⌋⌋⌋⌋
∴ \therefore ∴ ⌊ x P k + 1 ⌋ \lfloor \frac{x}{P_{k+1}} \rfloor ⌊Pk+1x⌋ = = = ⌊ ⌊ ⌊ ⌊ ⌊ x a 1 ⌋ a 2 ⌋ . . . ⌋ a k ⌋ a k + 1 ⌋ \lfloor \frac{\lfloor \frac{\lfloor \frac{\lfloor \frac{\lfloor \frac{x}{a_1} \rfloor}{a_2}\rfloor}{...} \rfloor}{a_k} \rfloor}{a_{k+1}}\rfloor ⌊ak+1⌊ak⌊...⌊a2⌊a1x⌋⌋⌋⌋⌋
得证。
这样就证完啦~ 所以就可以放心用 树剖 维护了。
注意这里是维护边权,实际上和维护点权也差不多,就有两个地方要注意:
-
1.由于树中每个结点的父边不会超过一条,因此一条边就唯一对应着它的孩子。所以我们就可以拿一条边的孩子结点的编号来作为此边的编号。
-
2.正因这种编号方式,在做链更新 / 查询的时候,有两个地方要注意:(下面代码也有相应注释)
-
① 在函数末尾(两个点处于同一条链上了),线段树的操作区间起点就不再是像维护点权那样,是位置高的结点的 i d id id 了,而是位置高的结点的儿子的 i d id id(否则就多操作位置高结点的父边了。那条边实际上不应该干涉到的)。不过区间终点还是和维护点权时的一样(一定会包含位置低结点的父边,也就是当前操作涉及的最下面的一条边)。(还有就是注意在函数的 while \text{while} while 循环内,操作区间的起点和终点都还是和维护点权时的一样。因为循环内部是在不断跨链,所以位置低的那个点往上跳时,其当前重链的 top \text{top} top 结点的父边的边权也需要统计,所以刚好就得操作那整条链,就和维护点权时一模一样了)。
图例:(粗线表示重边)
-
② 在函数即将返回的时候还需特判一下(就是说当两点重合的时候,对于维护点权得加上这个点的权,而对于维护边权就什么也不能加,因为没走边啊)
图例:
-
除了得考虑维护边权和维护点权的不同方面,还得考虑到可能会爆 long long \text{long long} long long,所以还不能无脑乘,必须特判溢出的情况。
考虑到
2
60
=
2
10
∗
6
=
102
4
6
>
100
0
6
=
1
0
18
2^{60} = 2^{10*6} = 1024^{6} > 1000^6 = 10^{18}
260=210∗6=10246>10006=1018,因此可以利用位操作(实际上利用了 __builtin_clzll
函数)来特判是否超出
1e18
\text{1e18}
1e18,若超出则直接赋值为
1ull<<60
\text{1ull<<60}
1ull<<60 即可。
再考虑到卡常问题,就不用 朴素线段树 实现了。又由于是单点修改,又维护的是乘积和整除关系,且还有特判,所以用 树状数组 也不够方便。因此只好拿 ZKW线段树 伺候之。
时间复杂度: O ( m log 2 n ) O(m \log^2 n) O(mlog2n)
解法2
上述解法实在过于暴力(强行树剖嘤 )… 实际上考虑到整除的削减作用,如果累计走了
60
60
60 条及以上边权
>
1
>1
>1 的边,那么就肯定只能得到
0
0
0 了。如果没
60
60
60 条边,那就直接暴力啊?!
所以直接利用 并查集 把边权为 1 1 1 的给缩掉就能直接做啦。
[A] 代码1:ZKW线段树 + 边权重链剖
1A \text{1A} 1A 真的爽爆!
/*
https://cn.vjudge.net/contest/302537#problem/A
https://codeforces.com/problemset/problem/593/D
ZKW线段树 + 树剖: 311ms
*/
#include <cstdio>
#include <cstring>
#include <climits>
#define IL inline
#define LL long long
#define ULL unsigned LL
#define _BS 1048576
char _bf[_BS];
char *__p1=_bf,*__p2=_bf;
#define _IO char *_p1=__p1,*_p2=__p2;
#define _OI __p1=_p1,__p2=_p2;
#ifdef _KEVIN
#define GC getchar()
#else
#define GC (_p1==_p2&&(_p2=(_p1=_bf)+fread(_bf,1,_BS,stdin),_p1==_p2)?EOF:*_p1++)
#endif
#define PC putchar
#define _Q_(x) {register char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;
#define _H_(x) for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define sc(x) _Q_(x)_H_(x)
#define se(x) _Q_(x)else if(_c==EOF)return 0;_H_(x)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define FED(u) for(const Ed*p=head[u]; p; p=p->next)
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
#define PL(_) PRT(_),PC(10)
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}
#define UPL(_) UPRT(_),PC(10)
#define MAX(a, b) ((a)>(b)?(a):(b))
#define SWAP(a, b) do{auto __tp=a; a=b; b=__tp;}while(0);
#define overflow(a, b) (__builtin_clzll(a) + __builtin_clzll(b) <= 66)
#ifdef _KEVIN
constexpr int MV(2e3+7), ME(MV<<1);
#else
constexpr int MV(2e5+7), ME(MV<<1);
#endif
constexpr ULL THRESHOLD(1ull << 60);
typedef ULL dint;
struct Ed
{
int u, v;
int son;
dint d;
Ed *next;
} *head[MV], ed[ME];
int tot;
#define edd(uu, vv, dd) ed[++tot].next=head[uu], ed[tot].u=uu, ed[tot].v=vv, ed[tot].d=dd, head[uu]=ed+tot
int de[MV], fa[MV], sz[MV], son[MV];
void dfs1(const int u, const int f)
{
de[u] = de[fa[u]=f] + (sz[u]=1);
int msz = 0;
FED(u)
{
if (p->v != f)
{
dfs1(p->v, u);
sz[u] += sz[p->v];
if (sz[p->v] > msz)
msz = sz[p->v], son[u] = p->v;
}
}
}
int top[MV], id[MV], dnt;
dint a0[MV], a[MV];
void dfs2(const int u)
{
top[u] = son[fa[u]]==u ? top[fa[u]] : u;
id[u] = ++dnt;
a[dnt] = a0[u];
if (son[u])
{
dfs2(son[u]);
FED(u)
if (p->v != fa[u] && p->v != son[u])
dfs2(p->v);
}
}
template <typename vint, typename pint, typename xint = int>
class ZKW
{
private:
xint n;
pint t[MV << 1]; // ZKW,两倍空间即可
xint _offset;
#define _getl(l) l-_offset
#define _getr(r) r+1-_offset
#define li i<<1
#define ri i<<1|1
#define pu(i) \
({ \
if (overflow(t[li], t[ri])) \
t[i] = THRESHOLD; \
else \
t[i] = t[li] * t[ri]; \
})
public:
void build(pint *arr, const xint l, const xint r)
{
_offset = l;
n = r-l+1;
memcpy(t+n, arr+_offset, n*sizeof(pint));
for (int i=n-1; i; --i)
pu(i);
}
void set(xint p, const vint v)
{
for (t[p=_getl(p)+n]=v; p>1; p>>=1)
{
if (overflow(t[p], t[p^1]))
{
do t[p>>=1] = THRESHOLD; // 一旦溢出,则向上一直溢出
while (p>1);
return;
}
t[p>>1] = t[p] * t[p^1];
}
}
pint prod(xint l, xint r) const
{
pint p = 1;
for (l=_getl(l)+n, r=_getr(r)+n; l<r; l>>=1, r>>=1)
{
if (l&1)
{
if (overflow(p, t[l]))
return THRESHOLD;
p *= t[l];
++l;
}
if (r&1)
{
--r;
if (overflow(p, t[r]))
return THRESHOLD;
p *= t[r];
}
}
return p;
}
};
ZKW<dint, dint> t;
namespace HLD
{
void set(const int e, const dint d)
{
t.set(id[ed[e<<1].son], d);
}
dint prod(int u, int v)
{
dint prod = 1, tp;
while (top[u] != top[v])
{
if (de[top[u]] > de[top[v]])
SWAP(u, v);
tp = t.prod(id[top[v]], id[v]); // 【跨链的情况】维护边权和维护点权在这里的询问是一样的,因为跨链,所以top[v]的父向边也要统计进来
if (overflow(prod, tp))
return THRESHOLD;
else
prod *= tp;
v = fa[top[v]]; // 位置靠下的向上跳
}
if (u == v) // 【同点特判】维护边权,必加此特判。对于维护点权,当询问链的起点终点重合时,应累加上此点点权;而对于维护边权,当询问链的起点终点重合时则应什么都不加(根本就没经过边)
return prod;
if (de[u] > de[v])
SWAP(u, v);
tp = t.prod(id[son[u]], id[v]); // 【同链的情况】维护边权,注意起点不是像维护点权那样是id[u],而是id[son[u]],因为当uv同链且u上v下时,u的父向边不应该被统计
return overflow(prod, tp) ? THRESHOLD : prod * tp;
}
}
int main()
{
_IO
get(V, q)
int u, v;
dint d;
for (int e=1; e<V; ++e)
{
sc(u)sc(v)sc(d)
edd(u, v, d);
edd(v, u, d);
}
dfs1(1, 0);
for (int i=2; i<=tot; i+=2)
a0[ed[i].son = fa[ed[i].u]==ed[i].v ? ed[i].u : ed[i].v] = ed[i].d;
// a0[ed[i].son = de[ed[i].u] > de[ed[i].v] ? ed[i].u : ed[i].v] = ed[i].d; // 也可以,更深的结点肯定是儿子
// a0[ed[i].son = son[ed[i].v]==ed[i].u ? ed[i].u : ed[i].v] = ed[i].d; // 不行,因为v可以是u的普通儿子而不是重儿子
dfs2(1);
t.build(a, 1, dnt);
int op, e;
dint tp;
while (q--)
{
sc(op)
if (op == 1)
{
sc(u)sc(v)sc(tp)
PL(tp / HLD::prod(u, v));
}
else
{
sc(e)sc(tp)
HLD::set(e, tp);
}
}
return 0;
}
[A] 代码2:并查集
Problem B. 数颜色数,越数越多
[B] 题意
给定一棵树,结点数 ∈ [ 1 , 1 0 5 ] \in [1, 10^5] ∈[1,105],每个点有一个固定的颜色。
有 m ∈ [ 1 , 1 0 5 ] m \in [1, 10^5] m∈[1,105] 次询问,第 i i i 次询问在以 v i v_i vi 为根的子树中,有多少种颜色出现的次数至少为 k i k_i ki。
[B] 思路
D F S DFS DFS 序转化一下就变成了莫队模板题。
时间复杂度: O ( n 1.5 ) O(n^{1.5}) O(n1.5)
[B] 代码
#include <iostream>
#include <vector>
#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define _F0N(i,n) for(auto i=0;i<(n);++i)
#define _FLR(i,l,r) for(auto i=(l);i<(r);++i)
#define _gF(_1, _2, _3, _F, ...) _F
#define F(...) _gF(__VA_ARGS__,_FLR,_F0N, ...)(__VA_ARGS__)
#define _FD0(i,n) for(auto i=0;i<=(n);++i)
#define _FDL(i,l,r) for(auto i=(l);i<=(r);++i)
#define _gFD(_1, _2, _3, _FD, ...) _FD
#define FD(...) _gFD(__VA_ARGS__,_FDL,_FD0, ...)(__VA_ARGS__)
#define FED(src) for(const auto *p=head[src]; p; p=p->next)
#define FRC(R,C) for(int r=0;r<R;++r)for(int c=0;c<C;++c)
#define OPER1(T,x1,b1) inline bool operator<(const T&_o)const{return x1 b1 _o.x1;}
#define OPER2(T,x1,b1,x2,b2) inline bool operator<(const T&_o)const{return x1 b1 _o.x1||x1==_o.x1&&x2 b2 _o.x2;}
#define OPER3(T,x1,b1,x2,b2,x3,b3) inline bool operator<(const T&_o)const{return x1 b1 _o.x1||x1==_o.x1&&(x2 b2 _o.x2||x2==_o.x2&&x3 b3 _o.x3);}
#define IL inline
#define LL long long
#define ULL unsigned LL
#define PC putchar
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}
#define CON constexpr
#define T_CASE int T;sc(T)for(int CASE=1;CASE<=T;++CASE)
#define Tjj int T;sc(T)while(T--)
#define qjj int q;sc(q)while(q--)
#define cincout std::cin.tie(nullptr),std::cout.tie(nullptr),std::ios::sync_with_stdio(false)
#define EPS 1e-8
#define PI 3.141592653589793238
#define MAX_INT 2147483647
#define MIN_INT -2147483648
#define MAX_LL 9223372036854775807LL
#define MIN_LL -9223372036854775808LL
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3fLL
#define priority_queue priority_queue
#define unordered_ unordered_
using std::vector;
#define PB emplace_back
#define EB emplace_back
#define EBK else break
#define ALL(X) (X).begin(),(X).end()
#define SORT(X) std::sort(ALL(X))
#define SORTD(X) std::sort(ALL(X),std::greater<decltype((X)[0])>())
#define SWAP(a,b) ({auto _t=a;a=b;b=_t;})
#define mem0(a) memset(a,0,sizeof(a))
#define memf1(a) memset(a,-1,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))
#define GCD(a,b) ({auto __tp=a,__a=a,__b=b;while(__b){__tp=__a%__b;__a=__b;__b=__tp;}__a;})
CON int MV(1e5+7), ME(MV<<1);
int a[MV];
struct Ed
{
int v;
Ed *next;
} *head[MV], ed[ME];
int tot;
#define edd(uu, vv) ed[++tot].next=head[uu], ed[tot].v=vv, head[uu]=ed+tot
struct Node
{
int rt, id;
Node(void) { }
Node(const int rtt, const int idd) :
rt(rtt), id(idd) { }
};
LL ans[MV];
LL s[MV], cnt[MV];
vector<Node> Q[MV];
bool ins[MV];
void inc(const int u, const int fa)
{
++cnt[a[u]];
++s[cnt[a[u]]];
FED(u)
if (!ins[p->v] && p->v != fa)
inc(p->v, u);
}
void dec(const int u, const int fa)
{
--s[cnt[a[u]]];
--cnt[a[u]];
FED(u)
if (!ins[p->v] && p->v != fa)
dec(p->v, u);
}
int de[MV], fa[MV], sz[MV];
void dfs(const int u, const int f)
{
sz[u] = 1, fa[u] = f;
FED(u)
if (p->v != f)
dfs(p->v, u), sz[u] += sz[p->v];
}
void solve(const int u, const int f, const bool re)
{
int mv = 0;
FED(u)
if (p->v != f && sz[p->v] > sz[mv])
mv = p->v;
FED(u)
if (p->v != f && p->v != mv)
solve(p->v, u, false);
if (mv)
solve(mv, u, true), ins[mv] = true;
inc(u, f);
if (mv)
ins[mv] = false;
for (auto &q: Q[u])
ans[q.id] = s[q.rt];
if (!re)
dec(u, f);
}
int main()
{
get(V, q)
sc(a, 1, V+1)
int E = V - 1;
while (E--)
{
get(u, v)
edd(u, v);
edd(v, u);
}
F(id, q)
{
get(ui, ki)
Q[ui].EB(Node(ki, id));
}
de[0] = 0, sz[0] = 0;
dfs(1, 0);
solve(1, 0, false);
F(i, q)
printf("%lld\n", ans[i]);
return 0;
}
Problem C. 最小生成树,边权你来改
[C] 题意
给定一个无向连通图,点数边数均 ∈ [ 1 , 2 ∗ 1 0 5 ] \in [1, 2*10^5] ∈[1,2∗105]
要单独对每条边考虑调整边权,使得其一定会在图的最小生成树中出现。求最大可以调整到的值。
如果不存在最大值(怎么改都必定会出现)则输出 − 1 -1 −1
[C] 思路
非常神仙的一道题 qwq
按照学长的思路点拨,我们先随便做一个 MST \text{MST} MST,然后分树边和非树边两类边进行考虑(学到了):
- 对于不在 MST \text{MST} MST 上的非树边:这种边的两个端点必定都在 MST \text{MST} MST 上( MST \text{MST} MST 一定会覆盖每个点)。那么考虑在把它连接后所产生的唯一的那个环,只要他的权值比这个环上的最大权树边的权值小 1 1 1,他就能一定能取代掉那条树边,一直出现在 MST \text{MST} MST 中(可用反证法证明)。因此非树边的答案可通过查询树上某条链上的最大值得到。
- 对于在 MST \text{MST} MST 上的树边:刚才我们分析了树边什么时候会被取代,所以如果某条树边想保住他的地位,就得考虑一切成环后环会包含他的那些非树边。这些非树边的边权都不能 < = <= <= 他,否则就可以取代他(注意等号!)。那具体怎么求解呢?显然不能以树边为核心,对于每条树边都把所有非树边看一遍,看哪些成环后会包含他,然后再取最小值再 − 1 -1 −1 得到答案。这样显然是 O ( n 2 ) O(n^2) O(n2) 的。所以我们还是得以非树边为核心。我们对于每条树边维护一个最小值域,每次我们求解非树边的答案时,除了查询链最大值,我们还得拿他的权值去更新已经这条链上的每条树边记录的最小值(从未记录的话就直接赋值上去)。这也是线段树的常规操作(类似区间赋值)。全部更新完后我们单点查询这个域就可以得到每条树边的答案啦。(如果他从来都没有被更新过那么说明没有树边连接后成的环会包含他,那么他一定是桥,答案就是 − 1 -1 −1)
因此 树剖 处理这三个操作(查最大、查最小、更新记录最小)就行啦。
时间复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
[C] 代码
400 \text{400} 400 行树剖… 爽是爽但是真的有丶吓人 qwq
/*
* If we give,
* all we've got,
* we will make it through.
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <limits>
using std::vector;
using std::sort;
#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define _F0N(i,n) for(auto i=0;i<(n);++i)
#define _FLR(i,l,r) for(auto i=(l);i<(r);++i)
#define _gF(_1, _2, _3, _F, ...) _F
#define F(...) _gF(__VA_ARGS__,_FLR,_F0N, ...)(__VA_ARGS__)
#define _FD0(i,n) for(auto i=0;i<=(n);++i)
#define _FDL(i,l,r) for(auto i=(l);i<=(r);++i)
#define _gFD(_1, _2, _3, _FD, ...) _FD
#define FD(...) _gFD(__VA_ARGS__,_FDL,_FD0, ...)(__VA_ARGS__)
#define FED(src) for(Ed *p=head[src]; p; p=p->next)
#define OPER1(T,x1,b1) inline bool operator<(const T&_o)const{return x1 b1 _o.x1;}
#define OPER2(T,x1,b1,x2,b2) inline bool operator<(const T&_o)const{return x1 b1 _o.x1||x1==_o.x1&&x2 b2 _o.x2;}
#define IL inline
#define LL long long
#define ULL unsigned LL
#define PC putchar
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}
#define UPL(_) UPRT(_),PC(10)
template<class T>
void PRT(const T _){if(_<0){PC(45),UPRT<ULL>(-(ULL)_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
#define PL(_) PRT(_),PC(10)
#define MIN(a, b) ((a)<(b)?(a):(b))
#define MIN3(a, b, c) (MIN(a, MIN(b, c)))
#define MAX(a, b) ((a)>(b)?(a):(b))
#define MAX3(a, b, c) (MAX(a, MAX(b, c)))
#define MAXER(a, b) ({if(a<b)a=b;a;})
#define MINER(a, b) ({if(a>b)a=b;a;})
#define MAXA(bg, ed) ({auto __mv=(bg);for(auto __it=__mv+1,__ed=(ed);__it!=__ed;++__it)if(*__it>*__mv)__mv=__it;__mv;})
#define MINA(bg, ed) ({auto __mv=(bg);for(auto __it=__mv+1,__ed=(ed);__it!=__ed;++__it)if(*__it<*__mv)__mv=__it;__mv;})
#define EB emplace_back
#define ALL(X) (X).begin(),(X).end()
#define SWAP(a,b) ({auto _t=a;a=b;b=_t;})
#define mem0(a) memset(a,0,sizeof(a))
#define memf1(a) memset(a,-1,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))
constexpr int MV(2e5+7), ME(MV<<1);
using dint = int;
constexpr dint SINT_MAX = std::numeric_limits<dint>::max(), SINT_MIN = std::numeric_limits<dint>::min();
int uf[MV];
int find(const int x)
{
return uf[x] ? uf[x]=find(uf[x]) : x;
}
bool merge(int x, int y)
{
if ((x=find(x)) == (y=find(y)))
return false;
return uf[x] = y, true;
}
struct Ed
{
Ed *next;
int u, v, son;
dint d;
} *head[MV], ed[ME];
int tot;
#define edd(uu, vv, dd) ed[++tot].next=head[uu], ed[tot].u=uu, ed[tot].v=vv, ed[tot].d=dd, head[uu]=ed+tot
struct OEd
{
int u, v, id;
dint d;
bool picked;
OPER1(OEd, d, <)
};
vector<OEd> edges;
void kruskal()
{
sort(edges.begin(), edges.end());
for (auto &e : edges)
if (merge(e.u, e.v))
e.picked = true, edd(e.u, e.v, e.d), edd(e.v, e.u, e.d);
}
template <typename vint, typename xint = int>
class STree
{
private:
static constexpr xint ROOT = 1;
struct Node
{
xint l, r;
vint min, max, lzm;
} t[MV << 2];
vint *a, vv;
xint ll, rr, pos;
#define glr xint li=i<<1, ri=i<<1|1
#define t_mid ((t[i].l+t[i].r)>>1)
#define ini_v(i, v) \
({ \
t[i].min = SINT_MAX, t[i].max = v; \
})
#define set_min_v(i, m) \
({ \
MINER(t[i].min, m); \
MINER(t[i].lzm, m); \
})
#define pu(i) \
({ \
t[i].max = MAX(t[li].max, t[ri].max); \
t[i].min = MIN(t[li].min, t[ri].min); \
})
#define pd(i) \
({ \
if (t[i].lzm != SINT_MAX) \
{ \
set_min_v(li, t[i].lzm); \
set_min_v(ri, t[i].lzm); \
t[i].lzm = SINT_MAX; \
} \
})
void build(const xint i, const xint l, const xint r)
{
t[i].l = l, t[i].r = r, t[i].lzm = SINT_MAX;
if (l == r)
ini_v(i, a[r]);
else
{
glr;
build(li, l, t_mid);
build(ri, t_mid+1, r);
pu(i);
}
}
void set_min(const xint i)
{
if (ll <= t[i].l && t[i].r <= rr)
set_min_v(i, vv);
else
{
glr;
pd(i);
if (ll <= t_mid)
set_min(li);
if (rr > t_mid)
set_min(ri);
pu(i);
}
}
vint _min(const xint i)
{
if (t[i].l == t[i].r)
return t[i].min;
else
{
glr;
pd(i);
if (pos <= t_mid)
return _min(li);
else
return _min(ri);
}
}
vint max(const xint i)
{
if (ll <= t[i].l && t[i].r <= rr)
return t[i].max;
else
{
glr;
pd(i);
vint m1 = SINT_MIN, m2 = SINT_MIN;
if (ll <= t_mid)
m1 = max(li);
if (rr > t_mid)
m2 = max(ri);
return MAX(m1, m2);
}
}
public:
void build(vint *arr, const xint l, const xint r)
{
a = arr, build(ROOT, l, r);
}
void set_min(const xint l, const xint r, const vint v)
{
ll = l, rr = r, vv = v,
set_min(ROOT);
}
vint min(const xint p)
{
pos = p;
return _min(ROOT);
}
vint max(const xint l, const xint r)
{
ll = l, rr = r;
return max(ROOT);
}
};
namespace HLD
{
STree<dint> st;
dint a0[MV];
int de[MV], fa[MV], sz[MV], son[MV];
void dfs1(const int u, const int f)
{
de[u] = de[fa[u]=f] + (sz[u]=1), son[u] = 0;
FED(u)
{
const int v = p->v;
if (v != f)
{
a0[p->son=v] = p->d;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]])
son[u] = v;
}
else
p->son = u;
}
}
dint a1[MV];
int top[MV], id[MV], dnt;
void dfs2(const int u)
{
top[u] = son[fa[u]]==u ? top[fa[u]] : u;
a1[id[u]=++dnt] = a0[u];
if (son[u])
{
dfs2(son[u]);
FED(u)
if (p->v == p->son && p->v != son[u])
dfs2(p->v);
}
}
void decom()
{
dfs1(1, 0), dfs2(1);
st.build(a1, 2, dnt);
}
void set_min(int u, int v, const dint d)
{
while (top[u] != top[v])
{
if (de[top[u]] > de[top[v]])
SWAP(u, v);
st.set_min(id[top[v]], id[v], d);
v = fa[top[v]];
}
if (u == v)
return;
if (de[u] > de[v])
SWAP(u, v);
st.set_min(id[son[u]], id[v], d);
}
dint min(const int u, const int v)
{
return st.min(id[de[u]>de[v] ? u:v]); // 边的编号是儿子结点,也就是深度大的
}
dint max(int u, int v)
{
dint m = SINT_MIN, tp;
while (top[u] != top[v])
{
if (de[top[u]] > de[top[v]])
SWAP(u, v);
tp = st.max(id[top[v]], id[v]);
MAXER(m, tp);
v = fa[top[v]];
}
if (u == v)
return m;
if (de[u] > de[v])
SWAP(u, v);
tp = st.max(id[son[u]], id[v]);
return MAX(m, tp);
}
} // namespace HLD
dint ans[MV];
int main()
{
get(V, E)
OEd ee;
ee.picked = false;
F(i, E)
{
sc(ee.u)sc(ee.v)sc(ee.d)
ee.id = i;
edges.EB(ee);
}
kruskal();
HLD::decom();
for (auto &e : edges)
{
if (not e.picked)
{
HLD::set_min(e.u, e.v, e.d);
ans[e.id] = HLD::max(e.u, e.v) - 1;
}
}
for (auto &e : edges)
{
if (e.picked)
{
dint min_d = HLD::min(e.u, e.v);
ans[e.id] = min_d==SINT_MAX ? -1 : min_d-1;
}
}
F(i, E)
PRT(ans[i]), PC(32);
return 0;
}
Problem D. 红蓝树,只能逐渐变红
[D] 题意
给定一棵树,结点数为 n ≤ 1 0 5 n\le 10^5 n≤105,编号从 1 \text{1} 1 开始,每个点不是蓝色就是红色,一开始只有 1 1 1 是红色。
接下来有 m ≤ 1 0 5 m\le 10^5 m≤105 次操作,操作有两种,一种是指定某个点把它的颜色设成红色,另一种是查询某个点的最近红点到他的距离。
[D] 思路
也是相当机智 并且够莽 才能想到对操作分块了吧 qwq
先预处理好 ST表 准备查 LCA \text{LCA} LCA。
然后考虑到询问和点数是同阶的,我们维护一个容量为 s q r t ( n ) sqrt(n) sqrt(n) 的修改操作的队列,同时用一个数组记录当前每个点最近红点距离的值。
每来一个询问我们就就暴力求解,即求 min { \min \{ min{数组中记录的值,操作队列中每个红点到当前点的距离(用 LCA \text{LCA} LCA) } \} }
每来一个修改我们就入队,如果队满我们就拿着整个队的点做 BFS 去更新那个数组,然后清空队列。
时间复杂度: O ( n 1.5 ) O(n^{1.5}) O(n1.5)
[D] 代码
分块大法好!
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long
#define ULL unsigned LL
#define _BS 1048576
char _bf[_BS];
char *__p1=_bf,*__p2=_bf;
#define _IO char *_p1=__p1,*_p2=__p2;
#define _OI __p1=_p1,__p2=_p2;
#ifdef _KEVIN
#define GC getchar()
#else
#define GC (_p1==_p2&&(_p2=(_p1=_bf)+fread(_bf,1,_BS,stdin),_p1==_p2)?EOF:*_p1++)
#endif
#define PC putchar
#define _Q_(x) {register char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;
#define _H_(x) for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define sc(x) _Q_(x)_H_(x)
#define se(x) _Q_(x)else if(_c==EOF)return 0;_H_(x)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}
#define UPL(_) UPRT(_),PC(10)
template<class T>
void PRT(const T _){if(_<0){PC(45),UPRT<ULL>(-(ULL)_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
#define PL(_) PRT(_),PC(10)
#define SWAP(a, b) do{auto __tp=a; a=b; b=__tp;}while(0)
constexpr int MV(1e5+7), ME(MV << 1), MQ(MV), MN(MV << 1);
struct Ed
{
int v, ne;
} ed[ME];
int tot, head[MV];
#define edd(uu, vv) ed[++tot].v=vv, ed[tot].ne=head[uu], head[uu]=tot
#define FED(u) for (int i=head[u]; i; i=ed[i].ne)
int de[MV];
int dfn[MN], dnt, first[MV];
void dfs(const int u, const int fa)
{
de[u] = de[fa] + 1;
dfn[++dnt] = u, first[u] = dnt;
FED(u)
{
const int v = ed[i].v;
if (v != fa)
dfs(v, u), dfn[++dnt] = u;
}
}
namespace RMQ
{
using vint = int;
constexpr int POW2[]{0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000, 0x1000000, 0x2000000, 0x4000000, 0x8000000, 0x10000000, 0x20000000, 0x40000000};
#define builtin_log2(x) (31-__builtin_clz(x))
vint dp[builtin_log2(MN) + 3][MN];
void init(void)
{
memcpy(dp[0], dfn, sizeof(*dfn) * (dnt+2));
for (int k=1, kk=builtin_log2(dnt); k<=kk; ++k)
for (int left=1, max_left=dnt-POW2[k-1]; left<=max_left; ++left)
dp[k][left] = de[dp[k-1][left]] < de[dp[k-1][left + POW2[k-1]]] ? dp[k-1][left] : dp[k-1][left + POW2[k-1]];
}
inline vint min(int l, int r)
{
if (l > r)
SWAP(l, r);
const int kk = builtin_log2(r-l+1);
return de[dp[kk][l]] < de[dp[kk][r+1-POW2[kk]]] ? dp[kk][l] : dp[kk][r+1-POW2[kk]];
}
}
inline int lca(const int u, const int v)
{
return RMQ::min(first[u], first[v]);
}
int dist[MV];
bool vis[MV];
int q[MQ], hd, tl;
void bfs(const int V)
{
while (hd != tl)
{
const int u = q[hd++];
FED(u)
{
const int v = ed[i].v;
if (!vis[v])
{
vis[q[tl++]=v] = true;
if (dist[v] > dist[u]+1)
dist[v] = dist[u]+1;
}
}
}
hd = tl = 0;
memset(vis, false, sizeof(*vis) * (V+2));
}
int get_ans(const int u)
{
for (int i=hd; i<tl; ++i)
{
const int d = de[u]+de[q[i]] - 2*de[lca(u, q[i])];
if (dist[u] > d)
dist[u] = d;
}
return dist[u];
}
int main()
{
#ifdef _VSC_KEVIN
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
_IO
get(V, k)
const int B_SZ = sqrt(V);
memset(dist, 0x3f, sizeof(*dist) * (V+2));
dist[1] = 0, vis[q[tl++]=1] = true;
int E = V-1;
while (E--)
{
get(u, v)
edd(u, v);
edd(v, u);
}
dfs(1, 0);
RMQ::init();
while (k--)
{
get(op, u)
if (op == 1)
{
if (dist[u])
dist[u] = 0, vis[q[tl++]=u] = true;
if (tl-hd >= B_SZ)
bfs(V);
}
else
PL(dist[u]<=1 ? dist[u] : get_ans(u));
}
return 0;
}
Problem E. xor树,会递归的xor
[E] 题意
给定一棵树,点权是 0 \text{0} 0 或 1 \text{1} 1,结点数为 n ≤ 1 0 5 n\le 10^5 n≤105,编号从 1 \text{1} 1 开始, 1 \text{1} 1 是根。
每个点可以进行一次操作,就是把自己以及其子树中到自己距离为偶正数的结点的值异或 1 \text{1} 1。
给出每个点的初始和目标点权,问最少需要操作几次以及要操作哪几个结点,可让每个点的点权变成目标值(保证可以变成)。
[E] 思路
发现奇偶互不影响,所以干脆直接重新分配爸爸(以爷爷为爸爸),然后建出一个森林。
然后就有点像 POJ 开灯那道题了… 对每棵树从根往下 DFS/BFS \text{DFS/BFS} DFS/BFS,如果当前点的值和目标值不一样那么就必须要翻转,否则之后就改不了它了。
注意要传递祖先方向的影响(就比如我本身不用改但是我的祖先们改了奇数次,那我也被翻转了,我也要改了)。
时间复杂度: O ( n ) O(n) O(n)
[E] 代码
hhh 我 dfs \text{dfs} dfs 了三次。 C++11 \text{C++11} C++11 真香。
/*
* If we give,
* all we've got,
* we will make it through.
*/
#include <cstdio>
#include <cstring>
#include <vector>
#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define _FD0(i,n) for(auto i=0;i<=(n);++i)
#define _FDL(i,l,r) for(auto i=(l);i<=(r);++i)
#define _gFD(_1, _2, _3, _FD, ...) _FD
#define FD(...) _gFD(__VA_ARGS__,_FDL,_FD0, ...)(__VA_ARGS__)
#define FED(src) for(const auto *p=head[src]; p; p=p->next)
#define FRC(R,C) for(int r=0;r<R;++r)for(int c=0;c<C;++c)
#define PC putchar
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
using std::vector;
#define EB emplace_back
constexpr int MV(2e5+7), ME(MV<<1);
struct Ed
{
int v;
Ed *next;
} *head[MV], ed[ME];
int tot;
#define edd(uu, vv) ed[++tot].next=head[uu], ed[tot].v=vv, head[uu]=ed+tot
int fa1[MV];
void dfs1(const int u, const int f)
{
fa1[u] = f;
FED(u)
if (p->v != f)
dfs1(p->v, u);
}
int fa2[MV];
vector<int> roots;
void dfs2(const int u)
{
if (!(fa2[u] = fa1[fa1[u]]))
roots.emplace_back(u);
FED(u)
if (p->v != fa1[u])
dfs2(p->v);
}
int is_bad[MV], reved[MV];
int q[MV];
vector<int> ans;
void dfs3(const int u)
{
if (is_bad[u]) // 只要我是坏的,不管上面怎么样,我一定是处于被翻转的状态(上面翻转奇数次,那我就是好的了,但仍然需要下传翻转;上面翻转偶数次,我还是坏的,我要再翻转一次,需要下传翻转)
reved[u] = true;
if (reved[fa2[u]] ^ is_bad[u]) // 我是坏的但上面翻转了偶数次,那么我要操作;我是好的但上面翻转了奇数次,我也要操作;其他情况我不用操作
ans.EB(u);
FED(u)
dfs3(p->v);
}
int main()
{
get(V)
int E = V-1;
while (E--)
{
get(u, v)
edd(u, v);
edd(v, u);
}
FD(u, 1, V)
sc(is_bad[u])
FD(u, 1, V)
{
get(tp)
is_bad[u] ^= tp;
}
dfs1(1, 0);
dfs2(1);
memset(head, 0, sizeof(*head) * (tot+2)), tot = 0;
FD(u, 1, V)
if (fa2[u])
edd(fa2[u], u);
for (auto rt : roots)
dfs3(rt);
PRT(ans.size()), PC(10);
for (auto u : ans)
PRT(u), PC(10);
return 0;
}
继续加强练习!