【LOJ502】[LibreOJ β Round] ZQC 的截图 (随机化)

真的是神仙题目啊……

题目

LOJ502

官方题解

我认为官方题解比我讲得好。

分析

这是一道蒙特卡洛算法的好题

上面那个奇奇怪怪的词是从官方题解里看到的,意思大概就是随机化算法 …… ?

一句话题意:给一棵有根树,每个点有一种颜色。每次加一个叶子,询问从根到这个叶子的路径上是不是所有颜色的出现次数都是 \(3\) 的倍数。如果不是,再询问是不是只有一个点不是 \(3\) 的倍数。如果只有一个点不是 \(3\) 的倍数,再询问这个点的编号。强制在线。

思考部分分似乎对想到正解没有什么帮助(谁能想到这题是随机化啊 wq ),所以直接来说正解。

考虑给每种颜色分配一个长为 \(w\) 的随机向量作为权值(我取\(w=40\) 。如果你像我一样没学过高中数学对向量不熟悉,就理解成一个长为 \(w\) 的数组好了)。把根到叶子路径上所有点的颜色的权值相加,每一维分别在模 \(3\) 意义下进行。如果和的每一维(可以理解成数组的每一个元素)都是 \(0\) ,那么说明所有颜色的出现次数都是 \(3\) 的倍数。这个算法的正确率证明如下:

在模域下,若干个随机的数加起来的结果是一个随机数(这里的「随机」指模域内每个数概率相等,下同);在模质数域下,随机数乘上一个非 \(0\) 整数仍然是随机数。而由于一堆随机数按特定顺序拼在一起就是一个随机向量,所以在模质数域下随机向量的加、数乘(乘数非 \(0\) )的结果仍然是随机向量。回到本题。如果不是所有颜色的出现次数都是 \(3\) 的倍数,那么和就相当于所有出现次数不是 \(3\) 的倍数的颜色的权值的 \(1\) 倍或 \(2\) 倍之和。由于 \(3\) 是质数,根据上述性质,和仍然是一个随机向量。这个随机向量是 \(\vec{0}\) 的概率是 \(\frac{1}{3^w}\) ,非常小。

如何判断是否只有一种颜色不是 \(3\) 的倍数呢?如果只有一种颜色不是 \(3\) 的倍数,那么和就是这种颜色的权值的 \(1\) 倍或 \(2\) 倍。那么直接判断一下是否存在一种颜色的权值是和的 \(1\) 倍或 \(2\) 倍(模 \(3\) 意义下乘 \(4\) 相当于乘 \(1\) )即可。用哈希表维护。

这样做的正确率如何呢?如果有不止一种颜色出现的次数不是 \(3\) 的倍数,和就是一个随机向量。和与一种颜色的权值的 \(1\) 倍或 \(2\) 倍相等的概率为 \(1-(1-\frac{2}{3^w})^{n}\) 。直观感受一下感觉挺小的(毕竟 \(3^{40}=12,157,665,459,056,928,801‬\)

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <cstdlib>
using namespace std;

namespace zyt
{
	template<typename T>
	inline bool read(T &x)
	{
		char c;
		bool f = false;
		x = 0;
		do
			c = getchar();
		while (c != EOF && c != '-' && !isdigit(c));
		if (c == EOF)
			return false;
		if (c == '-')
			f = true, c = getchar();
		do
			x = x * 10 + c - '0', c = getchar();
		while (isdigit(c));
		if (f)
			x = -x;
		return true;
	}
	template<typename T>
	inline void write(T x)
	{
		static char buf[20];
		char *pos = buf;
		if (x < 0)
			putchar('-'), x = -x;
		do
			*pos++ = x % 10 + '0';
		while (x /= 10);
		while (pos > buf)
			putchar(*--pos);
	}
	typedef unsigned long long ull;
	const int N = 1e6 + 10, M = 2e6 + 10;
	int lastans = 0, n, m;
	struct bitset_3
	{
		const static int B = 32;
		ull data;
		bitset_3(const ull &b = 0)
			: data(b) {}
		void set(const int a, const int x)
		{
			data &= ~((1ULL << (a << 1)) | (1ULL << (a << 1 | 1)));
			data |= (ull(x & 3) << (a << 1));
		}
		int query(const int a) const
		{
			return (data >> (a << 1)) & 3;
		}
		void rand()
		{
			for (int i = 0; i < B; i++)
				set(i, std::rand() % 3);
		}
		bool operator == (const bitset_3 &b) const
		{
			return data == b.data;
		}
		bitset_3 operator ^ (const bitset_3 &b) const
		{
			bitset_3 ans;
			for (int i = 0; i < B; i++)
				ans.set(i, (query(i) + b.query(i)) % 3);
			return ans;
		}
		ull to_ull() const
		{
			return data;
		}
		bool empty() const
		{
			return !data;
		}
	}w[N], v[M];
	namespace Hash_Table
	{
		const int P = 999983;
		struct edge
		{
			bitset_3 to;
			int w, next;
		}e[N];
		int head[P], ecnt;
		void init()
		{
			memset(head, -1, sizeof(head));
			ecnt = 0;
		}
		void add(const int a, const bitset_3 &b, const int c)
		{
			e[ecnt] = (edge){b, c, head[a]}, head[a] = ecnt++;
		}
		void add(const bitset_3 &b, const int c)
		{
			add(b.to_ull() % P, b, c);
		}
		int query(const bitset_3 &b)
		{
			for (int i = head[b.to_ull() % P]; ~i; i = e[i].next)
				if (e[i].to == b)
					return e[i].w;
			return -1;
		}
	}
	int work()
	{
		Hash_Table::init();
		srand(30624700);
		read(n), read(m);
		for (int i = 1; i <= n; i++)
		{
			w[i].rand();
			Hash_Table::add(w[i], i);
		}
		for (int i = 1; i <= m; i++)
		{
			int u, fa, tmp;
			read(u), read(fa);
			u ^= lastans, fa ^= lastans;
			v[i] = (v[fa] ^ w[u]);
			if (v[i].empty())
				write(lastans = -1);
			else if (~(tmp = Hash_Table::query(v[i])))
				write(lastans = tmp);
			else if (~(tmp = Hash_Table::query(v[i] ^ v[i])))
				write(lastans = tmp);
			else
				write(lastans = -2);
			putchar('\n');
		}
		return 0;
	}
}
int main()
{
	return zyt::work();
}
posted @ 2019-12-09 10:18  Inspector_Javert  阅读(246)  评论(0编辑  收藏  举报