【LOJ502】[LibreOJ β Round] ZQC 的截图 (随机化)
真的是神仙题目啊……
题目
我认为官方题解比我讲得好。
分析
这是一道蒙特卡洛算法的好题
上面那个奇奇怪怪的词是从官方题解里看到的,意思大概就是随机化算法 …… ?
一句话题意:给一棵有根树,每个点有一种颜色。每次加一个叶子,询问从根到这个叶子的路径上是不是所有颜色的出现次数都是 \(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();
}