• 【bzoj3091】城市旅行 LCT区间合并


    题目描述

    输入

    输出

    样例输入

    4 5
    1 3 2 5
    1 2
    1 3
    2 4
    4 2 4
    1 2 4
    2 3 4
    3 1 4 1
    4 1 4

    样例输出

    16/3
    6/1


    题解

    LCT区间合并

    前三个操作都是LCT的基本操作,可以LCT水过;重点在于第四个操作。

    考虑一个长度为n的序列,它的子区间个数为$sumlimits_{i=1}^ni=frac{n(n-1)}2$,只需要维护每个子区间的长度之和即可。

    考虑如果已经知道了左右子树的信息以及当前节点信息,如何更新当前子树的信息。需要解决区间合并问题。

    答案除了原来两子树答案之和以外,考虑1~n序列中的第i个点,它对答案的贡献是 左边个数*右边个数$i(n-i+1)$ ,而在合并后它右边的元素多了$si[rs]+1$,故答案增加了$sumlimits_{iin ls}rank[i]*w[i]$。所以需要记录一个数组$lv[]$,它表示 子树中每个点的权值*该点在子树中从左向右的排名(正排名) 之和。

    对于右边的点,同理,记录一个$rv[]$表示 子树中每个点的权值*该点在子树中从右向左的排名(逆排名) 之和。

    于是就可以使用这两个数组更新总答案。

    然后考虑怎么更新这两个数组,$lv$数组pushup时只涉及到当前节点和右子树的节点,每个节点的排名增加了$si[ls]+1$,所以$lv$除了两子树的$lv$之和外还要加上$(w[x]+sum[rs])*(si[ls]+1)$。

    所以对于每个节点,维护$si,w,sum,lv,rv,tv$,分别为子树大小、当前节点权值、当前子树权值和、权值*正排名、权值*逆排名、答案(子区间总长)。

    然后思考add&pushdown怎样进行:$sum$包含$si$个节点的贡献,所以加上$si*tag$;$lv,rv$包含$sumlimits_{i=1}^{si}i=frac{si(si+1)}2$个节点的贡献,所以加上$frac{si(si+1)*tag}2$;$tv$包含$sumlimits_{i=1}^nsumlimits_{j=1}^ij=frac{n(n+1)(n+2)}6$的贡献,所以加上$frac{n(n+1)(n+2)*tag}6$。

    知道了pushup和add&pushdown怎么写以后本题就水了,直接split出链后取出$tv$,与$frac{si(si+1)}2$作比即为答案。

    但 是 有 一 点 需 要 注 意 , 写 findroot 时 必 须 找 Splay tree 的 根 , 而 不 是 原 树 根 , 否 则 会 因 为 某 些 玄 学 原 因 而 无 限 TLE !

    感觉说了这么多也没有直接看代码来的直白~

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 50010
    using namespace std;
    typedef long long ll;
    int fa[N] , c[2][N] , rev[N];
    ll si[N] , w[N] , sum[N] , lv[N] , rv[N] , tv[N] , tag[N];
    void rever(int x)
    {
    	swap(c[0][x] , c[1][x]) , swap(lv[x] , rv[x]) , rev[x] ^= 1;
    }
    void add(int x , ll a)
    {
    	w[x] += a , sum[x] += a * si[x];
    	lv[x] += a * si[x] * (si[x] + 1) / 2;
    	rv[x] += a * si[x] * (si[x] + 1) / 2;
    	tv[x] += a * si[x] * (si[x] + 1) * (si[x] + 2) / 6;
    	tag[x] += a;
    }
    void pushup(int x)
    {
    	int l = c[0][x] , r = c[1][x];
    	si[x] = si[l] + si[r] + 1;
    	sum[x] = sum[l] + sum[r] + w[x];
    	lv[x] = lv[l] + lv[r] + (w[x] + sum[r]) * (si[l] + 1);
    	rv[x] = rv[l] + rv[r] + (w[x] + sum[l]) * (si[r] + 1);
    	tv[x] = tv[l] + tv[r] + lv[l] * (si[r] + 1) + rv[r] * (si[l] + 1) + w[x] * (si[l] + 1) * (si[r] + 1);
    }
    void pushdown(int x)
    {
    	int l = c[0][x] , r = c[1][x];
    	if(rev[x]) rever(l) , rever(r) , rev[x] = 0;
    	if(tag[x]) add(l , tag[x]) , add(r , tag[x]) , tag[x] = 0;
    }
    bool isroot(int x)
    {
    	return c[0][fa[x]] != x && c[1][fa[x]] != x;
    }
    void update(int x)
    {
    	if(!isroot(x)) update(fa[x]);
    	pushdown(x);
    }
    void rotate(int x)
    {
    	int y = fa[x] , z = fa[y] , l = (c[1][y] == x) , r = l ^ 1;
    	if(!isroot(y)) c[c[1][z] == y][z] = x;
    	fa[x] = z , fa[y] = x , fa[c[r][x]] = y , c[l][y] = c[r][x] , c[r][x] = y;
    	pushup(y) , pushup(x);
    }
    void splay(int x)
    {
    	update(x);
    	while(!isroot(x))
    	{
    		int y = fa[x] , z = fa[y];
    		if(!isroot(y))
    		{
    			if((c[0][y] == x) ^ (c[0][z] == y)) rotate(x);
    			else rotate(y);
    		}
    		rotate(x);
    	}
    }
    void access(int x)
    {
    	int t = 0;
    	while(x) splay(x) , c[1][x] = t , pushup(x) , t = x , x = fa[x];
    }
    int findroot(int x)
    {
    	while(fa[x]) x = fa[x];
    	return x;
    }
    void makeroot(int x)
    {
    	access(x) , splay(x) , rever(x);
    }
    void link(int x , int y)
    {
    	if(findroot(x) != findroot(y)) makeroot(x) , fa[x] = y;
    }
    void split(int x , int y)
    {
    	makeroot(x) , access(y) , splay(y);
    }
    void cut(int x , int y)
    {
    	split(x , y);
    	if(fa[x] == y) fa[x] = c[0][y] = 0 , pushup(y);
    }
    ll gcd(ll a , ll b)
    {
    	return b ? gcd(b , a % b) : a;
    }
    int main()
    {
    	int n , m , i , opt , x , y;
    	ll z , t;
    	scanf("%d%d" , &n , &m);
    	for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &w[i]) , pushup(i);
    	for(i = 1 ; i < n ; i ++ ) scanf("%d%d" , &x , &y) , link(x , y);
    	while(m -- )
    	{
    		scanf("%d%d%d" , &opt , &x , &y);
    		if(opt == 1) cut(x , y);
    		else if(opt == 2) link(x , y);
    		else if(opt == 3)
    		{
    			scanf("%lld" , &z);
    			if(findroot(x) == findroot(y)) split(x , y) , add(y , z);
    		}
    		else
    		{
    			if(findroot(x) == findroot(y))
    			{
    				split(x , y) , z = tv[y] , t = si[y] * (si[y] + 1) / 2;
    				printf("%lld/%lld
    " , z / gcd(z , t) , t / gcd(z , t));
    			}
    			else puts("-1");
    		}
    	}
    	return 0;
    }
    

     

  • 相关阅读:
    博客园添加侧边栏小插件并更改css样式
    ubuntu14.04环境下利用docker搭建solrCloud集群
    使用Grunt 插件打包Electron Windows应用
    使用Squirrel创建基于Electron开发的Windows 应用安装包
    Electron实战:创建ELectron开发的window应用安装包
    6、创建-查看-复制-删除-文件和文件夹相关命令
    5、Linux 系统基本文件管理
    3、Linux 获取帮助的方法-关机命令-7个系统启动级别
    2 、Linux基本命令-ls-pwd-cd-date-hwclock
    1 、Linux-Rhel6终端介绍-Shell提示符
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7412271.html
Copyright © 2020-2023  润新知