计蒜客 An Easy Problem On The Trees (2018 ICPC亚洲区域赛网络赛 南京 F)(动态树维护联通块大小)

版权声明:Why is everything so heavy? https://blog.csdn.net/lzc504603913/article/details/82390531

In this problem you will be given a tree with NN nodes and N - 1N−1 bidirectional edges. Now you should handle three kinds of operations.

11. Given two integers x, yx,y, you should make a new edge between these two node xx and yy if they belong to different component before.

22. Given two integers x, yx,y, if they belong to the same component, you should find the tree in the tree set which contains node xx, and you should make the node xx be the root of this tree, and then you should cut the edge between node yy and its father.

33. Given one integer xx. It means that if there is one point on the node xx initially, and then it moves to all adjacent nodes with the same probability, it will keep moving like this until it goes back to the node xx. You should output the expected number of edges that it will go through.

Input

The first line contains two integers NN and MM which means there are NN nodes in the tree and MM operations you need to handle.

In the next N - 1N−1 lines, each line contains two integers xx and yy, representing an edge between xx and yy.

The next MM lines start with an integer t (t\ in\ {1, 2, 3})t(t in 1,2,3), representing the kind of this operation.

It's guaranteed that 1 \le N, M \le 100000, 1 \le x, y \le N1≤N,M≤100000,1≤x,y≤N and the NN nodes form a tree initially.

Output

For operation 11 and 22, if the operation is illegal, you should output -1−1.

For operation 33, if there is no other node in the component which node xx belongs to, you should output 00.

Otherwise you should output the the expected number of edges modulo 998244353998244353 in the format p*q^{-1}p∗q−1.

It's guaranteed that the the answer is always p / qp/q (pp and qq are two coprime integers).

样例输入复制

2 4
1 2
1 1 2
3 1
2 1 2
3 2

样例输出复制

-1
2
0

题目来源

ACM-ICPC 2018 南京赛区网络预赛

题意:给一棵树,要求支持以下操作:

1. u v 连边

2. 以 u 为根,把 v 和它父亲的边切开

3. 询问以 u 为根,一个生物在 u,以等概率移动到相邻结点,问最后回到 u 的经过边数的期望。

大佬题解:蒙特卡洛可以发现询问就是求 (sz[u]-1)/d[u]*2,其中 sz[u] 是联通块大小,d[u] 是度数。然后就是一个很经典的维护子树大小的 lct 了。

关于LCT维护子树大小参阅博客:https://blog.csdn.net/wu_tongtong/article/details/78648537

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <set>
#include <vector>
#include <map>
#include <stack>
#include <set>
#include <algorithm>
#include <bitset>
#include <queue>
using namespace std;
typedef long long ll;
const int MAXN = 100005;
const int INF = 0x3f3f3f3f;
const ll MOD = 998244353;

ll mpow(ll a, ll b)
{
    ll t, y;
    t = 1;
    y = a;
    while (b != 0)
    {
        if (b & 1 == 1)
            t = t * y % MOD;
        y = y * y % MOD;
        b = b >> 1;
    }
    return t;
}

int Q[MAXN];
int ch[MAXN][2];
int fa[MAXN];
int rev[MAXN];

int siz[MAXN];
int sz[MAXN];
int degree[MAXN];

bool isroot(int rt)
{
    return ch[fa[rt]][0] != rt && ch[fa[rt]][1] != rt;
}

bool get(int x) { return ch[fa[x]][1] == x; }

void pushup(int rt)
{
    if (!rt)
        return;
    siz[rt] = siz[ch[rt][0]] + siz[ch[rt][1]] + sz[rt] + 1;
}

//下推函数
void pushdown(int x)
{
    if (rev[x])
    {
        rev[ch[x][0]] ^= 1;
        rev[ch[x][1]] ^= 1;
        rev[x] ^= 1;
        swap(ch[x][0], ch[x][1]);
    }
}

//Splay旋转函数,通常无需修改
void Rotate(int x)
{
    int old = fa[x], oldf = fa[old], op = get(x);
    if (!isroot(old))
        ch[oldf][ch[oldf][1] == old] = x; //这一条一定要放在改变父子关系之前!在纯Splay中是放在后面的,因为直接看oldf是否为0可知old是否为根。
    ch[old][op] = ch[x][op ^ 1];
    fa[ch[x][op ^ 1]] = old; //但这里使用isroot,改变之后就不能判断了!
    ch[x][op ^ 1] = old;
    fa[old] = x;
    fa[x] = oldf;
    pushup(old);
    pushup(x);
}
//Splay函数,将rt变为某棵Splay树的根节点,通常无需修改
void Splay(int x)
{
    int tp = 1;
    Q[1] = x;
    for (int i = x; !isroot(i); i = fa[i])
        Q[++tp] = fa[i]; //对于LCT的判断是否是根节点,需要使用isroot,在纯Splay中使用的是fa,不要搞混!
    for (int i = tp; i; i--)
        pushdown(Q[i]);
    for (int FA; !isroot(x); Rotate(x))
    {
        FA = fa[x];
        if (!isroot(FA))
            Rotate(get(x) == get(FA) ? FA : x);
    }
    pushup(x);
}

//打通x到整个LCT的根的路径,即fa和ch都是正确的
void Access(int x)
{
    int t = 0;
    while (x)
    {
        Splay(x);

        sz[x] += siz[ch[x][1]] - siz[t];

        ch[x][1] = t;
        pushup(x);
        t = x;
        x = fa[x];
    }
}
//把x变为整个LCT的根,先打通路径,然后把他变为他的Splay的根即可。
void Makeroot(int x)
{
    Access(x);
    Splay(x);
    rev[x] ^= 1;
}
//链接函数,先把x变为整个LCT的根(这时x才没有父亲,所以不能用Splay),然后再设置一个父亲即可
void Link(int x, int y)
{
    Makeroot(x);
    Makeroot(y);
    fa[x] = y;

    sz[y] += siz[x];
    pushup(y);
}

//剪断函数,根据Splay原理,可以把x变为y的左节点,然后删除即可
void Cut(int x, int y)
{
    Makeroot(x);
    Access(y);
    Splay(y);
    if (ch[y][0] == x)
    {
        fa[x] = ch[y][0] = 0;
        pushup(y);
    }
}
//分割函数,即把x到y这条路径变为一颗Splay树,从而把区间查询变为树上节点查询(Splay原理)
void split(int x, int y)
{
    Makeroot(y);
    Access(x);
    Splay(x);
}

//找到原树上的根
//具体实现:首先Access这个点,然后在Splay树中将这个点转到根
//由于Splay树按照深度为关键字排序,所以不断地向左子树寻找,就可以找到深度最小的根。
int find_root(int x)
{
    Access(x);
    Splay(x);
    int now = x;
    while (ch[now][0])
        now = ch[now][0];
    return now;
}

//找到原树上的父亲。
//具体实现:首先Access这个点,然后在Splay树中将这个点转到根
//由于y是这个Splay深度最大的点,所以他到根的路径信息都在左儿子
//这个点的父亲,必然是深度次大的点,所以不断往右走,即可找到他的父亲
int find_real_father(int v)
{
    Access(v);
    Splay(v);
    int nxt = ch[v][0];
    while (ch[nxt][1])
    {
        nxt = ch[nxt][1];
        pushdown(nxt);//记得下推
    }
    return nxt;
}

//查询x到y之间是否已经有路径,直接查询所在的LCT的根是否相等即可
int isconnected(int x, int y)
{
    int rx, ry;
    rx = find_root(x);
    ry = find_root(y);
    return rx == ry;
}

int main()
{

    int N, M;
    int u, v;
    scanf("%d%d", &N, &M);

    for (int i = 0; i < N - 1; i++)
    {
        scanf("%d%d", &u, &v);
        Link(u, v);
        degree[u]++;
        degree[v]++;
    }
    int op;
    for (int i = 0; i < M; i++)
    {
        scanf("%d", &op);

        if (op == 1)
        {
            scanf("%d%d", &u, &v);
            if (!isconnected(u, v) && u != v)
            {
                degree[v]++;
                degree[u]++;
                Link(u, v);
            }
            else
                printf("-1\n");
        }

        if (op == 2)
        {
            scanf("%d%d", &u, &v);
            if (isconnected(u, v) && u != v)
            {
                Makeroot(u);

                int rf = find_real_father(v);
                degree[v]--;
                degree[rf]--;
                Cut(v, rf);
            }
            else
                printf("-1\n");
        }

        if (op == 3)
        {
            scanf("%d", &u);
            Makeroot(u);
            if (siz[u] == 1)
                printf("0\n");
            else
                printf("%lld\n", ((siz[u] - 1) * mpow(degree[u], MOD - 2) % MOD) * 2 % MOD);
        }
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/lzc504603913/article/details/82390531