Comet OJ - Contest #11 D. isaster(kruskal重构树+线段树)

题意:

你需要支持对一张 nn 个点 mm 条边点带权的无向连通图进行以下两种操作:

  1. 修改点 x 的点权。
  2. 询问从点 x 出发只经过编号不大于 y 的点能到达的所有点的点权之积取模 998244353。
题解:

新的知识点:kruskal重构树
这个东西可以把一张无向图重构成一棵树,然后就可以很方便的得到某个点只走边权不超过y的边可以到达的点的集合(得到dfs序上的一个区间),然后维护这个区间的答案就可以了。这道题里把边权设成两端点中编号较大的那个的编号。
维护一个区间乘积的线段树,支持单点修改。

#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 1e6 + 50;
const int inf = 0x3f3f3f3f;
const ll mod = 998244353;
ll a[maxn];
int n, m, q;
int fa[maxn], tot, f[maxn][20], val[maxn];
int dfn[maxn], idx, id[maxn], sz[maxn];
int fnd(int x) {if(x == fa[x]) return x; return fa[x] = fnd(fa[x]);}
struct node{
    int u, v, w;
    node(){}; node(int a, int b, int c):u(a), v(b), w(c){}
    bool operator < (const node& a)const{return w < a.w;}
}e[maxn];
vector<int> g[maxn];
void mst(){//把图重构成树
    tot = n;
    sort(e, e+m);
    for(int i = 0; i < m; ++i){
        int u = fnd(e[i].u), v = fnd(e[i].v);
        if(u == v) continue;
        ++tot;
        fa[u] = fa[v] = tot;
        g[tot].push_back(u);
        g[tot].push_back(v);
        val[tot] = e[i].w;
        a[tot] = 1;
        fa[tot] = tot;
    }
}
void dfs(int u){
    sz[u] = 1;
    dfn[++idx] = u; id[u] = idx;
    for(int i = 1; i < 20; ++i) f[u][i] = f[f[u][i-1]][i-1];
    for(int i = 0; i < g[u].size(); ++i){
        int v = g[u][i];
        f[v][0] = u;
        dfs(v); sz[u] += sz[v];
    }
}
void init(){
    idx = 0;
    val[0] = inf;
    cin>>n>>m>>q;
    for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]), fa[i] = i;
    for(int i = 0; i < m; ++i){
        int u, v; scanf("%d%d", &u, &v);
        e[i] = node(u, v, max(u, v));
    }
    mst();
    dfs(tot);
}
ll sum[maxn<<2];
void up(int rt){
    sum[rt] = sum[rt<<1]*sum[rt<<1|1]%mod;
}
void build(int rt, int l, int r){
    if(l == r){
        sum[rt] = a[dfn[l]]%mod; return;
    }
    build(lson); build(rson);
    up(rt);
}
void update(int rt, int l, int r, int pos, ll x){
    if(l == r){
        sum[rt] = x; return;
    }
    if(pos <= mid) update(lson, pos, x);
    else update(rson, pos, x);
    up(rt);
}
ll qry(int rt, int l, int r, int L, int R){
    if(L <= l && r <= R) return sum[rt];
    ll ans = 1;
    if(L <= mid) ans = ans*qry(lson, L, R)%mod;
    if(R > mid) ans = ans*qry(rson, L, R)%mod;
    return ans;
}
void sol()
{
    build(1, 1, idx);
    while(q--){
        int op, x;
        ll y;
        scanf("%d%d%lld", &op, &x, &y);
        if(op == 1){
            if(x > y){
                printf("0\n"); continue;
            }
            for(int i = 19; i >= 0; --i){
               // cout<<f[x][i]<<endl;
                if(val[f[x][i]] > y) continue;
                //cout<<"x:"<<x<<endl;
                x = f[x][i];
                //cout<<"afterx:"<<x<<endl;
            }
            ll ans = qry(1, 1, idx, id[x], id[x] + sz[x] - 1);
            printf("%lld\n", ans%mod);
        }
        else{
            //assert(y != mod);
            update(1, 1, idx, id[x], y%mod);//注意这里y可能等于mod,所以不要用逆元去代换,直接修改即可!
        }
    }
}
int main()
{
    init();sol();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值