zoukankan      html  css  js  c++  java
  • new

    P3348 [ZJOI2016]大森林

    直接lct模拟(n^2logn)

    正解先有性质:

    • 性质1,每个0操作范围如果为(1~n),只要1操作正确(不会把生长节点移到不存在的节点),不会影响询问的答案
    • 性质2,对于每棵树,先修改完,再询问,答案也不会变

    所以我们离线处理,从前往后扫树,只维护一棵(lct),只需修改第(i)棵树与(i-1)棵的不同。

    对于生长节点的修改,建虚点,修改生长节点(x)次,就建(x+1)个虚点,将虚点连起来,最上面连着(1)号根节点(实点),(i)虚点侧面连上生长节点修改(i)次后(i+1)次前(0)操作新加的点(实点)。
    对于生长节点第(id)次被改,([l, r])树内生长节点改为了(x),首先根据([l, r])(x)存在的区间,确定出生长节点需要改为(x)的区间,
    离线处理时, 扫到第(l)棵树,其(id)号虚点(cut)掉父边,(link)(x), 扫到第(r + 1)棵树,其(id)号虚点(cut)掉父边,(link)(id-1)号虚点。
    每棵树先修改完再询问。

    对于询问,注意(dis[x,y])不能换根然后(split),要用差分找(dis)(dep[x]+dep[y]-2*dep[lca(x,y)]),(lca)(access)时候稍微维护一下就好,至于dep,只需记录该节点到根节点路径上实点的个数。

    为什么不能换根,因为这是有根树,我们要维护原树中确定的父子关系,才能正确的(cut)掉父边

    Code
    #include <bits/stdc++.h>
    #define rint register int
    #define F(x) (t[x].fa)
    #define L(x) (t[x].ch[0])
    #define R(x) (t[x].ch[1])
    #define get(x) (x == t[F(x)].ch[1])
    #define not_root(x) (x == t[F(x)].ch[0] || x == t[F(x)].ch[1])
    #define up(x) ({ t[x].sum = t[L(x)].sum + t[R(x)].sum + t[x].val; })
    
    const int maxn = 1e5, maxm = 2e5;
    
    int n, m, xc = 1, nc = 1, ac;
    int fwl[maxm], fwr[maxm];// 每个点的存在范围
    int ans[maxm];
    struct Node{
    	int val, sum, fa, ch[2];
    }t[maxm];
    struct Sim{
    	int pos, opt, x, y, z;
    	bool operator < (const Sim &B)const{
    		if(pos != B.pos) return pos < B.pos;
    		return opt < B.opt;
    	}
    }a[maxm * 2];
    struct Tmp{
    	int opt, l, r, x;
    }tmp[maxm];
    
    int read(rint x = 0, register bool f = 0, register char ch = getchar()){
    	for(;!isdigit(ch);ch = getchar()) f |= ch == '-';
    	for(; isdigit(ch);ch = getchar()) x = (x << 3) + (x << 1) + (ch & 15);
    	return f ? -x : x ;
    }
    
    void add(rint x, rint f, rint d){
    	if(x) F(x) = f;
    	if(f) t[f].ch[d] = x;
    }
    
    void rotate(rint x){
    	rint y = F(x), z = F(y), gx = get(x), gy = get(y);
    	if(not_root(y)) add(x, z, gy);
    	else F(x) = z;
    	add(t[x].ch[gx ^ 1], y, gx), add(y, x, gx ^ 1);
    	up(y);
    }
    
    void splay(rint x){
    	while(not_root(x)){
    		rint y = F(x);
    		if(not_root(y)) get(x) != get(y) ? rotate(x) : rotate(y);
    		rotate(x);
    	}
    	up(x);
    }
    
    int access(rint x){
    	rint y = 0;
    	for(;x;y = x, x = F(x)) splay(x), R(x) = y, up(x);
    	return y;//返回lca
    }
    
    void link(rint x, rint y){
    	splay(x), F(x) = y; // 不需要access,我们没有换根,所以这个x是其子树的父亲
    }
    void cut(rint x){
    	access(x), splay(x), L(x) = F(L(x)) = 0, up(x);//这里就需要access了,因为要让他父亲找到他
    }
    
    int cal(rint x, rint y){
    	rint ans = 0, lca;
    	access(x), splay(x), ans = t[x].sum;
    	lca = access(y), splay(y), ans += t[y].sum;
    	access(lca), splay(lca), ans -= t[lca].sum * 2;
    	return ans;
    }
    
    int main(){
    #ifdef lzx
    	freopen("1.in","r",stdin);
    #endif
    	n = read(), m = read();
    	fwl[1] = 1, fwr[1] = n; // 注意1的初始化
    	for(rint i = 1;i <= m; ++i){// 读取修改和询问,处理出虚点和实点的个数,实点编号1 ~ nc,虚点编号nc + 1 ~ xc
    		tmp[i].opt = read(), tmp[i].l = read(), tmp[i].r = read();
    		if(tmp[i].opt != 0) tmp[i].x = read();
    		switch(tmp[i].opt){
    			case 0: fwl[++nc] = tmp[i].l, fwr[nc] = tmp[i].r; break; // nc实点个数
    			case 1: ++xc; break; // 虚点个数
    		}
    	}
    	for(rint i = 1;i <= nc; ++i) t[i].val = 1;
    	link(nc + 1, 1); // 注意link传参的顺序不要变,父节点在后
    	for(rint i = 1, nown = 1, nowx = 1, l, r;i <= m; ++i){//先把初始的树建出来,
    		switch(tmp[i].opt){
    			case 0: link(++nown, nowx + nc); break;
    			case 1:
    				link(nowx + 1 + nc, nowx + nc), ++nowx;
    				l = std:: max(tmp[i].l, fwl[tmp[i].x]);
    				r = std:: min(tmp[i].r, fwr[tmp[i].x]);
    				if(l <= r){
    					a[++ac] = (Sim){l, 1, nowx + nc, tmp[i].x};
    					a[++ac] = (Sim){r + 1, 1, nowx + nc, nowx - 1 + nc};
    				}
    				break;
    			case 2: a[++ac] = (Sim){tmp[i].l, 2, tmp[i].r, tmp[i].x, i}; break;
    		}
    	}
    	std:: sort(a + 1, a + 1 + ac);
    	for(rint i = 1, j = 1;i <= n; ++i){//从前往后,对于一棵树,先处理link和cut,再处理询问
    		for(;a[j].pos == i; ++j){
    			if(a[j].opt == 1) cut(a[j].x), link(a[j].x, a[j].y);
    			if(a[j].opt == 2) ans[a[j].z] = cal(a[j].x, a[j].y);
    		}
    	}
    	for(rint i = 1;i <= m; ++i) if(tmp[i].opt == 2) printf("%d
    ", ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    mysql关联查询
    MySQL数据库面试题(转发来自https://thinkwon.blog.csdn.net/article/details/104778621)
    iview + vue + thinphp5.1 源码
    <!--标签嵌套规则-->
    PHP的基本变量检测方法
    PHP的八种变量
    php变量命名规范
    C++11新特性-常用
    算法设计-DP常见问题及解题技巧
    Web开发-概述
  • 原文地址:https://www.cnblogs.com/Lour688/p/14177127.html
Copyright © 2011-2022 走看看