zoukankan      html  css  js  c++  java
  • [洛谷P3401] 洛谷树

    洛谷题目连接:洛谷树

    题目背景

    萌哒的Created equal小仓鼠种了一棵洛谷树!

    (题目背景是辣鸡小仓鼠乱写的QAQ)。

    题目描述

    树是一个无环、联通的无向图,由n个点和n-1条边构成。树上两个点之间的路径被定义为他们之间的唯一一条简单路径——显然这是一条最短路径。

    现在引入一个概念——子路径。假设树上两个点p1和pn之间的路径是P=<p1,p2,p3,…,pn>,那么它的子路径被定义为某一条路径P’,满足P’=<pi,pi+1,pi+2,…,pj>,其中1<=i<=j<=n。显然,原路径是一条子路径,任意一个点也可以作为子路径。

    我们给每条边赋予一个边权。萌萌哒的Sugar问小仓鼠:对于任意两个点u和v,你能快速求出,u到v的路径上所有子路径经过的边的边权的xor值的和是多少。具体地说就是,你把u到v的路径上所有子路径全部提出来,然后分别把每个子路径上经过的边的边权xor在一起,最后求出得到的所有xor值的和。

    什么?你不知道xor?那就去百度啊!

    这时候,fjzzq2002大爷冒了粗来:窝还要你滋磁修改某条边边权的操作!

    小仓鼠那么辣鸡,当然不会做这道题啦。于是他就来向你求救!

    输入输出格式

    输入格式:

    第一行两个正整数n和q,表示点的个数,查询和询问的总次数。

    接下来n-1行,每行两个正整数u、v、w,表示u和v两个点之间有一条边权为w的边。

    接下来q行,格式为1 u v或2 u v w。如果为1 u v操作,你需要输出u到v的路径上所有子路径经过的边的边权的xor值的和是多少;如果为2 u v w操作,你需要把u到v这条边的边权改为w,保证这条边存在。

    输出格式:

    对于每个1操作,输出答案。

    输入输出样例

    输入样例#1:

    5 3
    1 2 3
    2 3 3
    2 4 6
    4 5 1
    1 3 4
    2 2 4 7
    1 3 5

    输出样例#1:

    14
    26

    说明

    本题时限1s,内存限制128M,因新评测机速度较为接近NOIP评测机速度,请注意常数问题带来的影响。

    【数据范围】

    No    n=    q=    备注
    1    100    5    无
    2    100    20    无
    3    100    100    无
    4    5000    1000    无
    5    5000    2000    无
    6    5000    3000    无
    7    10000    10000    第i条边连接第i个点和第i+1个点,且没有2操作
    8    10000    20000    第i条边连接第i个点和第i+1个点,且没有2操作
    9    10000    10000    第i条边连接第i个点和第i+1个点
    10    10000    20000    第i条边连接第i个点和第i+1个点
    11    10000    10000    没有2操作
    12    10000    20000    没有2操作
    13    20000    20000    没有2操作
    14    30000    30000    没有2操作
    15    30000    10000    无
    16    20000    20000    无
    17    20000    20000    无
    18    30000    20000    无
    19    20000    30000    无
    20    30000    30000    无
    

    对于100%的数据,所有边权小于等于1023。

    一句话题意: 给出一棵树,要求出一条路径上的所有子段的异或值的和.并且要求支持修改操作.

    题解: 要求一条路径上的子段的异或值的和,如果直接用朴素算法的话,需要枚举这个子段的两个端点.那么这样复杂度就是(O(n^2))的了.

    我们先来考虑这样一种情况:假设边的权值只有0和1.

    因为题目要求的是把所有子段的异或和加起来,所以先将每个点到根的异或和统计起来,用树剖维护到线段树中.因为0与0异或后仍然是0,1与1异或也是,而这样都不能对答案做出贡献,所以就不用考虑加入答案.只有一个0和一个1异或后的值为1,也就是说答案就是0与1可以形成的组合的数量,也就是从根节点开始的异或和为0的个数乘以1的个数.

    那么对于那些权值不是0和1的也可以转化成二进制的形式,然后再按位处理,也就是说,对于一条路径的查询可以分成11次查询的和(1023(leq2^{10})),分别查询第0~第10位的答案之和.

    然后对于修改操作,因为我们在线段树中维护的是异或和,所以需要将修改的那条边的子树中的值都要修改(对于二进制每一位的修改),因为只有0和1,所以直接将区间的信息翻转一下就可以了.

    可能讲的有点不清楚,可以通过样例模拟一下,然后看下代码理解一下吧.

    #include<bits/stdc++.h>
    #define ll(x) (x<<1)
    #define rr(x) (x<<1|1)
    using namespace std;
    typedef int _int;
    #define int long long
    const int N=30000+5;
    
    int n, m, ecnt = 0, last[N], val[N], pre[N];
    int size[N], son[N], fa[N], top[N], id[N], idx = 0, dep[N], vis[N];
    
    struct edge{
    	int to, nex, w;
    }e[N*2];
    
    struct segment_tree{
    	int l, r, size, res1, tag;
    	segment_tree operator + (const segment_tree a) const{
    		segment_tree temp;
    		temp.size = size+a.size;
    		temp.res1 = res1+a.res1;
    		return temp;
    	}
    }t[N*4][12];
    
    int gi(){
    	int ans = 0, f = 1; char i = getchar();
    	while(i<'0' || i>'9'){ if(i == '-') f = -1; i = getchar(); }
    	while(i>='0' && i<='9') ans = ans*10+i-'0', i = getchar();
    	return ans * f;
    }
    
    void add(int x, int y, int z){
    	e[++ecnt].to = y;
    	e[ecnt].w = z;
    	e[ecnt].nex = last[x];
    	last[x] = ecnt;
    }
    
    void up(int x, int k){ t[x][k].res1 = t[ll(x)][k].res1+t[rr(x)][k].res1; }
    
    void dfs1(int x, int f, int deep){
    	size[x] = 1, dep[x] = deep, fa[x] = f;
    	for(int to, i=last[x];i;i=e[i].nex){
    		to = e[i].to;
    		if(to == f) continue;
    		pre[to] = e[i].w^pre[x], val[to] = e[i].w;
    		dfs1(to, x, deep+1); size[x] += size[to];
    		if(size[to] > size[son[x]]) son[x] = to;
    	}
    }
    
    void dfs2(int x, int f, int tp){
    	top[x] = tp, id[x] = ++idx, vis[idx] = x;
    	if(son[x]) dfs2(son[x], x, tp);
    	for(int to, i=last[x];i;i=e[i].nex){
    		to = e[i].to;
    		if(to != f && to != son[x]) dfs2(to, x, to);
    	}
    }
    
    void build(int x, int l, int r, int k){
    	t[x][k].l = l, t[x][k].r = r, t[x][k].size = r-l+1;
    	if(l == r){
    		if((pre[vis[l]]>>k) & 1) t[x][k].res1 = 1;
    		return;
    	}
    	int mid = (l+r>>1);
    	build(ll(x), l, mid, k), build(rr(x), mid+1, r, k); up(x, k);
    }
    
    void pushdown(int x, int k){
    	if(t[x][k].tag == 0) return;
    	t[ll(x)][k].tag ^= 1, t[ll(x)][k].res1 = t[ll(x)][k].size-t[ll(x)][k].res1;
    	t[rr(x)][k].tag ^= 1, t[rr(x)][k].res1 = t[rr(x)][k].size-t[rr(x)][k].res1;
    	t[x][k].tag = 0;
    }
    
    void update(int x, int l, int r, int k){
    	if(l <= t[x][k].l && t[x][k].r <= r){
    		t[x][k].res1 = t[x][k].size-t[x][k].res1;
    		t[x][k].tag ^= 1;
    		return;
    	}
    	int mid = (t[x][k].l+t[x][k].r>>1); pushdown(x, k);
    	if(l <= mid) update(ll(x), l, r, k);
    	if(mid < r) update(rr(x), l, r, k); up(x, k);
    }
    
    segment_tree query(int x, int l, int r, int k){
    	if(l <= t[x][k].l && t[x][k].r <= r) return t[x][k];
    	segment_tree temp; temp.size = temp.res1 = 0;
    	if(r < t[x][k].l || t[x][k].r < l) return temp;
    	pushdown(x, k); temp.size = t[x][k].size;
    	return query(ll(x), l, r, k)+query(rr(x), l, r, k);
    }
    
    void modify(int x, int y, int v){
    	if(dep[x] < dep[y]) swap(x, y);
    	for(int i=0;i<=10;i++)
    		if(((v^val[x])>>i) & 1) update(1, id[x], id[x]+size[x]-1, i);
    	val[x] = v;
    }
    
    int ask(int xx, int yy){
    	int x, y, res = 0; segment_tree temp, check;
    	for(int i=0;i<=10;i++){
    		x = xx, y = yy; temp.size = temp.res1 = 0;
    		while(top[x] != top[y]){
    			if(dep[top[x]] < dep[top[y]]) swap(x, y);
    			temp = temp+(check=query(1, id[top[x]], id[x], i));
    			x = fa[top[x]];
    		}
    		if(id[x] > id[y]) swap(x, y);
    		temp = temp+(check=query(1, id[x], id[y], i));
    		res += (temp.size-temp.res1)*temp.res1*(1<<i);
    	}
    	return res;
    }
    
    _int main(){
    	int opt, x, y, z; n = gi(), m = gi();
    	for(int i=1;i<n;i++) x = gi(), y = gi(), z = gi(), add(x, y, z), add(y, x,  z);
    	dfs1(1, -1, 1); dfs2(1, -1, 1);
    	for(int i=0;i<=10;i++) build(1, 1, n, i);
    
    	for(int i=1;i<=m;i++){
    		opt = gi(), x = gi(), y = gi();
    		if(opt == 1) cout << ask(x, y) << endl;
    		else z = gi(), modify(x, y, z);
    	}
    	return 0;
    }
    
  • 相关阅读:
    HDU3746 Cyclic Nacklace KMP
    KMP的小结
    POJ1916 Period KMP
    POJ 2406 Power Strings 简单KMP模板 strcmp
    HDU 2157 How many ways?? (邻接矩阵快速幂)
    (VIJOS) VOJ 1067 Warcraft III 守望者的烦恼 矩阵快速幂
    puppet之自定义fact(转载)
    Shell标准输出、标准错误 >/dev/null 2>&1
    如何在linux中用命令产生一个范围内的随机数?
    SNMP协议
  • 原文地址:https://www.cnblogs.com/BCOI/p/9306135.html
Copyright © 2011-2022 走看看