NOIP2023模拟6联测27 B. 旅行

NOIP2023模拟6联测27 B. 旅行

题目大意

有一棵 n n n 个点, n n n 条边的基环树,每条边有一种颜色。

现在要修改 n n n 次颜色,询问每次修改颜色后的连通块的数量。

思路

我们发现修改就是先删除,再添加边。

考虑用一个 m a p i , j map_{i , j} mapi,j 维护与点 i i i 相连的,颜色为 j j j 的边的数量。

对于添加一条边 x , y , w {x , y , w} x,y,w

  • 如果 x , y x , y x,y 都有其他的边颜色为 w w w ,那么就要 a n s − − ans-- ans
  • 如果只有一个点有,那么 a n s ans ans 不变
  • 如果两个点都没有,那么 a n s + + ans ++ ans++
  • 如果 x , y x , y x,y 是环上的点且环上的边都是同一种颜色,那么 a n s ans ans 要加一,因为上面减了一

对于删除一条边 x , y , w x , y , w x,y,w 和加边操作类似,但是注意如果 x , y x , y x,y 在环上且这个环的颜色是同一种,那么连通块数量不变。

code

#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
using namespace std;
const int N = 3e5 + 5;
int hd[N] , cnt , stk[N] , top , sum[N] , dep[N] , cir[N] , cir1 , ans , n , m , flg[N << 1];
struct E {
    int to , nt , w;
} e[N << 1];
map<int , int> mp[N] , vis[N];
void add (int x , int y , int z) { e[++cnt].to = y , e[cnt].nt = hd[x] , e[cnt].w = z , mp[x][y] = cnt , hd[x] = cnt; }
void dfs (int x , int fa) {
    dep[x] = dep[fa] + 1;
    int y;
    stk[++top] = x;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (y == fa) continue;
        if (dep[y] && dep[y] < dep[x]) {
            while (stk[top] != y) {
                cir[++cir1] = stk[top];
                top--;
            }
            cir[++cir1] = y;
        }
        if (!dep[y]) {
            dfs (y , x);
        }
    }
    if (stk[top] == x)
        top --;
}
void Insert (int x , int y , int w , int z) {
    int tmp = (vis[x][w] != 0) + (vis[y][w] != 0);
    vis[x][w] ++;
    vis[y][w] ++;
    if (z)
        ++ sum[w];
    if (z && sum[w] == cir1)
        tmp --;
    ans += 1 - tmp;
}
void del (int x , int y , int w , int z) {
    vis[x][w] -- , vis[y][w] --;
    int tmp = (vis[x][w] != 0) + (vis[y][w] != 0);
    if (z && sum[w] == cir1)
        tmp--;
    if (z)
        sum[w] --;
    ans -= 1 - tmp;
}
int main () {
    freopen ("tour.in" , "r" , stdin);
    freopen ("tour.out" , "w" , stdout);
    int T , u , v , w , x;
    scanf ("%d" , &T);
    while (T --) {
        scanf ("%d%d" , &n , &m);
        cnt = 1;
        cir1 = top = 0;
        ans = 0;
        // dep[1] = 1;
        fu (i , 1 , n) hd[i] = dep[i] = sum[i] = 0 , mp[i].clear() , vis[i].clear();
        fu (i , 1 , n) {
            scanf ("%d%d%d" , &u , &v , &w);
            add (u , v , w) , add (v , u , w);
        }
        fu (i , 2 , cnt) flg[i] = 0;
        dfs (1 , 0);
        // cout << cir1 << "\n";
        // continue;
        fu (i , 1 , cir1)
            flg[mp[cir[i]][cir[i % cir1 + 1]]] = flg[mp[cir[i]][cir[i % cir1 + 1]] ^ 1] = 1;
        for (int i = 2 ; i <= cnt ; i += 2) 
            Insert (e[i].to , e[i ^ 1].to , e[i].w , flg[mp[e[i].to][e[i ^ 1].to]]);
        while (m --) {
            scanf ("%d%d%d" , &u , &v , &w);
            x = mp[u][v];
            del (u , v , e[x].w , flg[x]);
            Insert (u , v , w , flg[x]);
            e[x].w = e[x ^ 1].w = w;
            printf ("%d\n" , ans);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值