• Educational Codeforces Round 54 (Rated for Div. 2) DE


    D 给出一个无向图,需要删去一些边,想知道最后能有多少个点到1的距离还是过去那么短

    如果求一个最短路,然后从删边的角度看,看起来很难做,但是如果从零开始加边就会有做法,如同prim那样,先加入和1直接相连的边,这条边必定是cost最小的,因为只有这样才能保证连接的点是最短路,这也是prim的思想。

    当加入一个点a后,需要将a相连的边也加入考虑,假设有1->a a->b ,由于1->a已经被我们选了,那么假装有边1->b,在进行一下第一轮的选择,如果1->b被选了,那么其实选的是a->b,这样会发现,及时最后能留许多边,我们也只会选择n-1条,一个图上虽然有很多边,但是求完单源最短路后,一定有某种方法将这些最短路用一棵树来表示。

    看起来这个做法是prim的变种。。因为完全是把最短路当作树来考虑的,但是写完后想了一下。。这个做法一般被称为堆优化的dij。。。

    struct node{
        L v;//�����
        L c;//��������
        L bh ;
        node(L _v=0,L _c=0,L tp=0):v(_v),c(_c),bh(tp){};
        bool operator <(const node &r)const{
            return c>r.c;
        }
    };
    priority_queue<node>q ;
    
    vector<node> w[300050] ;
    vector<int>ans ;
    L d[300050] ;
    
    int main () {
        L n , m , k ;
        cin >> n >> m >> k ;
        for(L i = 1 ; i <= m ; i ++ ) {
            L u , v , c ;
            cin >> u >> v >> c ;
            w[u].pb(node(v,c,i)) ;
            w[v].pb(node(u,c,i)) ;
        }
        L us = 0 ;
        memset(d , -1 , sizeof(d)) ;
        d[1] = 0;
        for(L i = 0 ; i < w[1].size() ; i ++ ) {
            q.push(w[1][i]) ;
        }
        while(!q.empty()) {
            node f = q.top() ; q.pop() ;
            if(d[f.v] != -1) continue ;
            if(us >= k) break ;
            us ++ ;
            ans.pb(f.bh) ;
            d[f.v] = f.c ;
            for(L i = 0 ; i < w[f.v].size() ; i ++ ) {
                node tp ;
                tp.v = w[f.v][i].v ;
                tp.c = f.c + w[f.v][i].c ;
                tp.bh = w[f.v][i].bh ;
                q.push(tp) ;
            }
        }
        cout << us << endl ;
        sort(ans.begin() , ans.end()) ;
        show(ans) ;
        L z = 0 ;
    }
    

      

    E 给出一棵有根树,初始点权为0,每次操作将点v的0~d代孩子权值+x,最后输出每个点的权值

    dfs序之后,每个点都可以被看作一个二维平面上的点,(dfsid,depth),那么其实就是矩形加和,最后统一询问点权

    # 有很多办法可以做,不过因为矩形范围太大 3e5,使用KDT来写,但是wa了。。寻找bug   后续 我没找出来啊。。。绝望

    矩阵加和辣么经典。。于是采用线段树来写,思路是把所有的(l,r,up,dow,val)的加和拆分成(l,up,dow,val)和(r+1,up,dow,-val),从左到右维护一个线段树即可

    外国选手不流行lowbit,倒是造出了玄妙的东西,支持单点加,区间求和。

    const L mn = 300050 ;
    L ans[mn] ;
    L ll[mn] , rr[mn] , pre[mn] , d[mn];
    L tot ;
    
    vector<L > G[mn] ;
    vector<pii > Q[mn] ;
    
    
    void dfs(L u,L p) {
        tot ++ ;
        if (p == -1) d[u] = 1 ;
        ll[u] = tot ;
        pre[tot] = u ;
        for(L i = 0; i < G[u].size() ; i ++ ) {
            L v = G[u][i] ;
            if(v != p) {
                d[v] = d[u] + 1 ;
                dfs(v , u) ;
            }
        }
        rr[u] = tot ;
    }
    
    
    L su[mn * 4] ;
    L ma[mn * 4] ;
    void dow(L ro,L l,L r) {
        if(l == r) return ;
        ma[ro*2] += ma[ro] ;
        ma[ro*2+1] += ma[ro] ;
        su[ro*2] += ma[ro] ;
        su[ro*2+1] += ma[ro] ;
        ma[ro] = 0 ;
    }
    void upda(L ro,L l,L r,L ql,L qr,L x) {
        if (ql <= l && qr >= r) {
            su[ro] += x ;
            ma[ro] += x ;
            return ;
        }
        dow(ro,l,r) ;
        L mid = (l + r) / 2 ;
        if(ql <= mid) upda(ro*2,l,mid,ql,qr,x) ;
        if(qr >= mid + 1) upda(ro*2+1,mid+1,r,ql,qr,x) ;
        su[ro] = su[ro*2] + su[ro*2+1] ;
    }
    L query(L ro,L l,L r,L pos) {
        if(l == r) return su[ro] ;
        L mid = (l + r) / 2 ;
        dow(ro,l,r) ;
        if(pos <= mid) return query(ro*2,l,mid,pos) ;
        else return query(ro*2+1,mid+1,r,pos) ;
    }
    
    
    const L T = 1 << 19 ;
    
    L c[2*T+50] ;
    void add(L x,L v) {
        x += T ;
        while(x) {
            c[x] += v;
            x /= 2 ;
        }
    }
    L ask(L l,L r) {
        L res = 0 ;
        l += T , r += T ;
        while(l < r) {
            if (l & 1) {
                res += c[l] ;
            }
            if (!(r & 1)) {
                res += c[r] ;
            }
            l = (l + 1) >> 1 ;
            r = (r - 1) >> 1 ;
        }
        if (l == r)
            res += c[r] ;
        return res ;
    }
    
    
    int main () {
        L n ; cin >> n ;
        rep(i,1,n-1) {
            L u, v ; // cin >> u >> v ;
            scanf("%lld%lld" , &u, &v) ;
            G[u].pb(v) ;
            G[v].pb(u) ;
        }
        tot = 0 ;
        dfs(1,-1) ;
    
        L qnum ; cin >> qnum ;
        while(qnum -- ) {
            L xi , di , vi ;
            // cin >> vi >> di >> xi ;
            scanf("%lld%lld%lld" , &vi , &di , &xi) ;
            L dep = di + d[vi] ;
            dep = min(dep , n) ;
            Q[ ll[vi] ].push_back(make_pair(dep , xi)) ;
            Q[ rr[vi] + 1 ].push_back(make_pair(dep , -xi)) ;
        }
        flc(c,0) ;
        for(L i = 1 ; i <= n ; i ++ ) {
            for(L j = 0 ; j < Q[i].size() ; j ++ ) {
                pii p = Q[i][j] ;
                upda(1,1,n,1,p.first,p.second) ;
    //            add(p.first,p.second) ;
            }
            ans[ pre[i] ] = query(1,1,n,d[ pre[i] ]) ;
    //        ans[ pre[i] ] = ask(d[ pre[i] ] , n) ;
        }
        for(L i = 1 ; i <= n ; i ++ ) {
            printf("%lld" , ans[i]) ;
            if(i == n) printf("
    ") ; else printf(" ") ;
        }
    }
    

     

    --- 2020.2.3 ---

    因为没事干,所以看了一眼自己以前的博客,看看是否智商降低了,发现这个题有简单的做法,将有深度限制的子树加减转化成二维点跑矩阵加减其实是KDT题集里面学到的,然后陷入了数据结构的误区

    将树上点的值以dfsid作为数组存储,会发现每次操作是在[l,r]区间进行一次有[depl,depr]的限制加法,可以在l点加上+, 在r点加上-的操作

    从1~n扫一遍,维护一个线段树或树状数组,就行了。

  • 相关阅读:
    九度oj 题目1208:10进制 VS 2进制
    九度oj 题目1209:最小邮票数
    九度oj 题目1207:质因数的个数
    九度oj 题目1030:毕业bg
    九度oj 题目1014:排名
    九度oj 题目1048:判断三角形类型
    九度oj 题目1335:闯迷宫
    [Luogu] Tree
    点分治 算法学习 && [Poj] 1741
    [Luogu] 排序机械臂
  • 原文地址:https://www.cnblogs.com/rayrayrainrain/p/9956575.html
Copyright © 2020-2023  润新知