4-4模拟赛 重组病毒——LCT+树链剖分

2 篇文章 0 订阅
1 篇文章 0 订阅

前置技能点:LCT,线段树,DFS序

如果你不知道上面的东西,请先行了解

start_of_题面

【问题描述】

黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了
新型的重组病毒。这种病毒的繁殖和变异能力极强。为了阻止这种病毒传播,某
安全机构策划了一次实验,来研究这种病毒。

实验在一个封闭的局域网内进行。局域网内有n台计算机,编号为 1 ~ n 1~n 1n。一
些计算机之间通过网线直接相连,形成树形的结构。局域网中有一台特殊的计算
机,称之为核心计算机。根据一些初步的研究,研究员们拟定了一个一共m步的
实验。实验开始之前,核心计算机的编号为 1,每台计算机中都有病毒的一个变
种,而且每台计算机中的变种都不相同。实验中的每一步会是下面中的一种操作:

1、RELEASE x
在编号为x的计算机中植入病毒的一个新变种。这个变种在植入之前不存
在于局域网中。

2、RECENTER x
将核心计算机改为编号为x的计算机。但是这个操作会导致原来核心计算
机中的病毒产生新变种,并感染过来。换言之,假设操作前的核心计算
机编号为y,相当于在操作后附加了一次 RELEASE y 的操作。
根据研究的结论,在植入一个新变种时,病毒会在局域网中搜索核心计算机
的位置,并沿着网络中最短的路径感染过去。
而第一轮实验揭露了一个惊人的真相:病毒的不同变种是互斥的。新变种在
感染一台已经被旧变种感染的电脑时,会把旧变种完全销毁之后再感染。但研究
员发现了实现过程中的漏洞。如果新变种在感染过程中尚未销毁过这类旧变种,
需要先花费 1 单位时间分析旧变种,才能销毁。如果之前销毁过这类旧变种,就
可以认为销毁不花费时间。病毒在两台计算机之间的传播亦可认为不花费时间。
研究员对整个感染过程的耗时特别感兴趣,因为这是消灭病毒的最好时机。
于是在m步实验之中,研究员有时还会做出如下的询问:

3、REQUEST x
询问如果在编号为x的计算机的关键集合中的计算机中植入一个新变种,
平均感染时间为多长。编号为y的计算机在编号为x的计算机的关键集合
中,当且仅当从y沿网络中的最短路径感染到核心计算机必须经过x。由
于有 RECENTER 操作的存在,这个集合并不一定是始终不变的。

至此,安全机构认为已经不需要实际的实验了,于是他们拜托你编写一个程
序,模拟实验的结果,并回答所有的询问。

【输入格式】

输入的第一行包含两个整数 n n n m m m,分别代表局域网中计算机的数量,以及
操作和询问的总数。

接下来 n − 1 n − 1 n1行,每行包含两个整数 x x x y y y,表示局域网中编号为 x x x y y y的计算
机之间有网线直接相连。
接下来 m m m行,每行包含一个操作或者询问,格式如问题描述中所述。

【输出格式】

对于每个询问,输出一个实数,代表平均感染时间。输出与答案的绝对误差
不超过 1 0 − 6 10^{-6} 106 时才会被视为正确。

【样例输入】

8 6
1 2
1 3
2 8
3 4
3 5
3 6
4 7
REQUEST 7
RELEASE 3
REQUEST 3
RECENTER 5
RELEASE 2
REQUEST 1

【样例输出】

4.0000000000
2.0000000000
1.3333333333

【样例解释】

样例中的局域网如下图所示:
这里写图片描述
下面对每个操作进行解释。解释中直接用x代表编号为x的计算机。

  1. 7 的关键集合中只包含 7,而从 7 到 1 的最短路径上会遇到 4 种不同的
    变种(包括 7 本身的变种),故答案为 4。
  2. 在 3 中植入了新变种,感染了 3 和 1。(感染时间为 2)
  3. 3 的关键集合中包含 3、4、5、6 和 7,其感染时间分别为 1、2、2、2
    和 3,平均值为 2。
  4. 核心计算机改为 5,同时新变种感染了 1、3 和 5。(感染时间为 2)
  5. 在 2 中植入了新变种,感染了 2、1、3 和 5。(感染时间为 2)
  6. 1 的关键集合中包含 1、2 和 8,其感染时间分别为 1、1、2,平均值为
    4/3。

【数据规模和约定】

这里写图片描述

end_of_题面

昨天刷了一天LCT今天就来了道LCT,真不错

我觉得我已经LCT中毒了,一看这道题的操作就想LCT,发现直接可以做,每一个点的值就是这个点到根的虚边数量,RELEASE操作可以直接变成LCT的access操作,RECENTER就可以直接用makeroot代替。然后问题变成了查询,我们知道LCT维护链上的值非常简单,但是维护子树的值就有些问题了,~~而我又不会写TopTree,~~我们发现有很多部分的点没有换根操作,那么我们可以直接用一个树链剖分+线段树维护子树信息,我们只需要在access的过程中记录下需要修改的子树,然后在线段树上修改就可以了。

然后对于需要换根的点,我们可以考虑我们换的根和原来的根的关系,我记得还有另外一道LCT的题目用到了这个性质,有两种情况

这里我们用 r o o t root root表示当前的根, x x x表示询问的点, R R R表示我们树链剖分的根。

第一种情况, L C A ( x , r o o t ) = x LCA(x, root) = x LCA(x,root)=x,如图

这里写图片描述

这种情况,我们询问 x x x需要的信息是在 x x x的除了 v v v的其它子树和不在 x x x子树上的点,我们发现可以直接转化为DFS序上除了子树 v v v的部分的值,而子树 v v v在DFS序上会是连续的一段,所以我们查询的区间就会使分开的两段。

第二种情况, L C A ( x , r o o t ) ≠ x LCA(x, root) \neq x LCA(x,root)̸=x,如图
这里写图片描述

这个时候我们发现此时 x x x的答案与我们以 R R R为根树链剖分的 x x x的子树的贡献完全一样,我们直接在原来的DFS序上查询这个区间就可以了。

说起来还挺简单,但是写起来麻烦的一批,我一整场模拟赛就做了这一道题…结果发现隔壁的sekong_qi只用了两个小时就A了而且还顺便打了第三题的暴力,我因为答案炸int爆掉5分…

%%%sekong_qi

start_of_code

#include <cmath>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

#define LL long long

const int N = 300000 + 3000;

int n, m, root;
char str[10];

namespace file{
	inline void open()
	{
		freopen("recompile.in", "r", stdin);
		freopen("recompile.out", "w", stdout);
	}

	inline void close()
	{
		fclose(stdin);
		fclose(stdout);
	}
}

namespace input{
	inline int read()
	{
		int a = 0;
		int f = 1;
		char ch;
		while(!((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-')));
		if(ch == '-')
			f = -1;
		else
		{
			a = a * 10;
			a += ch - '0';
		}
		while((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-'))
		{
			a = a * 10;
			a += ch - '0';
		} 
		return a * f;
	}
}

namespace Edge{
	int F[N], v[N << 1], nex[N << 1], EID = 1;
	inline void add(int f, int t)
	{
		nex[EID] = F[f];
		v[EID] = t;
		F[f] = EID++;
	}
}

namespace Treec{
	int depth[N], dfn[N], l[N], r[N], sonn[N], siz[N];
	int DFN = 0, w[N], DATA[N];
	
	inline void prepare()
	{
		depth[1] = 1;
		root = 1;
	}
	
	inline void dfs(int p, int top, int fa)
	{
		l[p] = ++DFN;
		DATA[DFN] = w[p];
		if(sonn[p])
			dfs(sonn[p], top, p);
		for(int i = Edge::F[p];i;i = Edge::nex[i])
		{
			int t = Edge::v[i];
			if(t == fa || t == sonn[p])
				continue;
			dfs(t, t, p);
		}
		r[p] = DFN;
	}
}

namespace Seg{
	LL sum[N], lc[N], rc[N], tag[N];
	
	inline void pushdown(int p, int l, int r)
	{
		if(tag[p])
		{
			int mid = (l + r) >> 1;
			tag[p << 1] += tag[p];
			tag[p << 1 | 1] += tag[p];
			sum[p << 1] += 1ll * tag[p] * (mid - l + 1);
			sum[p << 1 | 1] += 1ll * tag[p] * (r - mid);
			tag[p] = 0;
		}
	}
	
	inline void update(int p)
	{
		sum[p] = sum[p << 1] + sum[p << 1 | 1];
	}
	
	inline void build(int p, int l, int r)
	{
		lc[p] = l, rc[p] = r;
		tag[p] = 0;
		if(l == r)
		{
			sum[p] = Treec::DATA[l];
			return;
		}
		int mid = (l + r) >> 1;
		build(p << 1, l, mid);
		build(p << 1 | 1, mid + 1, r);
		update(p);
	}
	
	inline void change(int p, int l, int r, LL x)
	{
		if(l == lc[p] && r == rc[p])
		{
			sum[p] += x * (r - l + 1) * 1ll;
			tag[p] += x;
			return;
		}
		pushdown(p, lc[p], rc[p]);
		int mid = (lc[p] + rc[p]) >> 1;
		if(r <= mid)
			change(p << 1, l, r, x);
		else if(l > mid)
			change(p << 1 | 1, l, r, x);
		else
			change(p << 1, l, mid, x), change(p << 1 | 1, mid + 1, r, x);
		update(p);
	}
	
	inline LL query(int p, int l, int r)
	{
		if(l == lc[p] && r == rc[p])
			return sum[p];
		pushdown(p, lc[p], rc[p]);
		int mid = (lc[p] + rc[p]) >> 1;
		if(r <= mid)
			return query(p << 1, l, r);
		else if(l > mid)
			return query(p << 1 | 1, l, r);
		else
			return query(p << 1, l, mid) + query(p << 1 | 1, mid + 1, r);
	}
}

namespace LCA{
	int f[20][N];
	
	inline void dfs(int p, int fa)
	{
		Treec::siz[p] = 1;
		Treec::sonn[p] = 0;
		Treec::w[p] = Treec::depth[p];
		for(int i = Edge::F[p];i;i = Edge::nex[i])
		{
			int t = Edge::v[i];
			if(t == fa)
				continue;
			f[0][t] = p;
			Treec::depth[t] = Treec::depth[p] + 1;
			dfs(t, p);
			if(Treec::siz[t] > Treec::siz[Treec::sonn[p]])
				Treec::sonn[p] = t;
			Treec::siz[p] += Treec::siz[t];
		}
	}
	
	inline void solve()
	{
		for(int j = 1;j <= 19;++j)
			for(int i = 1;i <= n;++i)
				f[j][i] = f[j - 1][f[j - 1][i]];
	}
	
	inline int lca(int x, int y)
	{
		if(Treec::depth[x] < Treec::depth[y])
			swap(x, y);
		for(int i = 19;i >= 0;--i)
			if(Treec::depth[f[i][x]] >= Treec::depth[y])
				x = f[i][x];
		if(x == y)
			return x;
		for(int i = 19;i >= 0;--i)
			if(f[i][x] != f[i][y])
			{
				x = f[i][x];
				y = f[i][y];
			}
		return f[0][x];
	}
	
	inline int Get(int x, int y)
	{
		for(int i = 19;i >= 0;--i)
			if(Treec::depth[f[i][x]] > Treec::depth[y])
				x = f[i][x];
		return x;
	}
}

inline void Modi(int p, LL w)
{
	int lc = LCA::lca(p, root);
	if(p == root)
		Seg::change(1, 1, n, w);
	else if(lc == p)
	{
		int v = LCA::Get(root, p);
		if(Treec::l[v] - 1 >= 1)
			Seg::change(1, 1, Treec::l[v] - 1, w);
		if(Treec::r[v] + 1 <= n)
			Seg::change(1, Treec::r[v] + 1, n, w);
	}
	else
		Seg::change(1, Treec::l[p], Treec::r[p], w);
}

namespace LCT{
	vector<int> chgminus, chgplus;
	int son[N][2], fa[N], rev[N], sum[N], L[N], R[N];
	
	inline int which(int x)
	{
		return x == son[fa[x]][1];
	}
	
	inline bool isroot(int x)
	{
		return x != son[fa[x]][0] && x != son[fa[x]][1];
	}
	
	inline void pushdown(int x)
	{
		if(rev[x])
		{
			if(son[x][0])
			{
				swap(son[son[x][0]][0], son[son[x][0]][1]);
				swap(L[son[x][0]], R[son[x][0]]);
				rev[son[x][0]] ^= 1;
			}
			if(son[x][1])
			{
				swap(son[son[x][1]][0], son[son[x][1]][1]);
				swap(L[son[x][1]], R[son[x][1]]);
				rev[son[x][1]] ^= 1;
			}
			rev[x] = 0;
		}
	}
	
	inline void update(int x)
	{
		if(son[x][0] == 0)
			L[x] = x;
		else
			L[x] = L[son[x][0]];
		if(son[x][1] == 0)
			R[x] = x;
		else
			R[x] = R[son[x][1]];
	}
	
	inline void rotate(int x)
	{
		int f = fa[x], g = fa[f], d = which(x);
		if(!isroot(f))
			son[g][which(f)] = x;
		son[f][d] = son[x][d ^ 1];
		fa[son[f][d]] = f;
		son[x][d ^ 1] = f;
		fa[f] = x;
		fa[x] = g;
		update(f);
		update(x);
	}
	
	int stk[N], top = 0;
	
	inline void splay(int x)
	{
		top = 0;
		stk[++top] = x;
		int fc;
		for(int i = x;!isroot(i);i = fa[i])
			stk[++top] = fa[i];
		for(int i = top;i >= 1;--i)
			pushdown(stk[i]);
		for(;!isroot(x);rotate(x))
			if(!isroot(fa[x]))
				rotate(which(x) == which(fa[x]) ? fa[x] : x);
	}
	
	inline void access(int x)
	{
		chgminus.clear();
		chgplus.clear();
		int y = 0;
		for(;x;y = x, x = fa[x])
		{
			splay(x);
			if(son[x][1])
				chgplus.push_back(L[son[x][1]]);
			son[x][1] = y;
			if(y)
				chgminus.push_back(L[y]);
			update(x);
		}
	}
	
	inline void makeroot(int x)
	{
		access(x);
		splay(x);
		swap(son[x][0], son[x][1]);
		swap(L[x], R[x]);
		rev[x] ^= 1;
	}
	
	inline void prepare()
	{
		for(int i = 1;i <= n;++i)
			fa[i] = LCA::f[0][i], L[i] = R[i] = i;
	}
}

namespace SOLVE{
	inline void request(int p)
	{
		int lc = LCA::lca(p, root);
		if(p == root)
		{
			LL ans = Seg::query(1, 1, n);
			printf("%.9f\n", (double)(ans) / n);
		}
		else if(lc == p)
		{
			int v = LCA::Get(root, p);
			LL ansl = 0, ansr = 0;
			if(Treec::l[v] - 1 >= 1)
				ansl = Seg::query(1, 1, Treec::l[v] - 1);
			if(Treec::r[v] + 1 <= n)
				ansr = Seg::query(1, Treec::r[v] + 1, n);
			printf("%.9f\n", (double)(ansl + ansr) / (n - Treec::siz[v]));
		}
		else
		{
			LL ans = Seg::query(1, Treec::l[p], Treec::r[p]);
			printf("%.9f\n", (double)(ans) / Treec::siz[p]);
		}
	}
	
	inline void release(int p)
	{
		LCT::access(p);
		int s = LCT::chgplus.size();
		for(int i = 0;i < s;++i)
			Modi(LCT::chgplus[i], 1);
		s = LCT::chgminus.size();
		for(int i = 0;i < s;++i)
			Modi(LCT::chgminus[i], -1);
	}
	
	inline void recenter(int p)
	{
		LCT::makeroot(p);
		int s = LCT::chgplus.size();
		for(int i = 0;i < s;++i)
			Modi(LCT::chgplus[i], 1);
		s = LCT::chgminus.size();
		for(int i = 0;i < s;++i)
			Modi(LCT::chgminus[i], -1);
		root = p;
	}
}

int main()
{
	file::open();
	n = input::read(), m = input::read();
	for(int i = 1;i < n;++i)
	{	
		int x = input::read(), y = input::read();
		Edge::add(x, y);
		Edge::add(y, x);
	}
	Treec::prepare();
	LCA::dfs(1, -1);
	LCA::solve();
	Treec::dfs(1, 1, -1);
	Seg::build(1, 1, n);
	LCT::prepare();
	for(int i = 1;i <= m;++i)
	{
		int x;
		scanf("%s%d", str, &x);
		if(str[2] == 'Q')
			SOLVE::request(x);
		if(str[2] == 'L')
			SOLVE::release(x);
		if(str[2] == 'C')
			SOLVE::recenter(x);
	}
	file::close();
	return 0;
}

end_of_code

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值