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("\n") ; else printf(" ") ; } }
--- 2020.2.3 ---
因为没事干,所以看了一眼自己以前的博客,看看是否智商降低了,发现这个题有简单的做法,将有深度限制的子树加减转化成二维点跑矩阵加减其实是KDT题集里面学到的,然后陷入了数据结构的误区
将树上点的值以dfsid作为数组存储,会发现每次操作是在[l,r]区间进行一次有[depl,depr]的限制加法,可以在l点加上+, 在r点加上-的操作
从1~n扫一遍,维护一个线段树或树状数组,就行了。