2023/3/16数论总结(未完)

CF1305F

随机化经典例题
考虑某题正解是随机化的话, 要满足什么? 正确率很高, 或者每次的正确率是一个比较大的数(比如 \(0.5\) )。
回到这道题, 我们首先考虑如果将所有数变成 \(2\) 的最小步数, 其一定小于等于 \(n\) , 这一点应该很明显, 最劣情况是所有数都是奇数, 那也只需要 \(n\) 步就可以使所有数满足条件。 既然如此, 我们考虑最后所有数变成了其它的数的因数, 会是什么情况。 此时, 假设这个因数是 \(x\) , 那么至少一半的数满足以下三种情况中的一个:其本身是 \(x\) 的倍数, 其加上一后是 \(x\) 的倍数, 其减去一后是 \(x\) 的倍数。如果不这样, 那么必然有大于 \(n/2\) 的数需要操作大于等于 \(2\) 次, 那么操作数就比操作成 \(2\) 的倍数的操作数要多了, 综上, 我们随机选择序中的一个数 \(a_i\) 那么我们有大于等于 \(1/2\) 的几率答案需要的因数是 \(a_i\)\(a_i + 1\)\(a_i - 1\) 的因数。 选多次后就有很大的几率选中正确答案。

SPOJ11414

这道题算是博弈论的板子题目
首先我们需要搞清楚 SG 函数, 这个函数其实就是把所有的操作双方等价操作的博弈问题转到 DAG 中后再转化成 NIM 游戏。 既然如此, 我们发现这就是典型的那种问题, 考虑构造 SG 函数。对于一个点,我们将其整个子树的 SG 函数设为 \(f(i)\) , 我们再设 \(i\) 子树中操作了一个点后的 SG 函数为 \(g(i, j)\) 。 发现 \(g(i, j)\) 其实就是几个互不相干的子树所组成的森林, 根据 SG 函数的性质, 直接转化成异或就行了, 考虑到子树需要挨个处理, 而对于 \(g(i, j)\) , 我们其实是可以直接转化成 \(g(fa(i), j)\) 的( \(fa(i)\) 表示 \(i\) 的父亲), 只需要再异或上 \(fa(i)\) 的其它儿子的 \(f(u)\) 的异或和就行了, 所以我们可以 \(trie\) 树合并, 具体过程就不再赘述。
代码:

#include <cstdio>
#include <algorithm>
#include <stack>
using namespace std;

#define MAXN 100000
#define MAXM 17

bool sc[MAXN + 5];
bool vis[MAXN + 5];
struct node {
	int ed;
	node *next;
}*s[MAXN + 5];
struct trie {
	int num = 0;
	int k = 0;
	int Lazy = 0;
	trie *next[2] = {};
}*rt[MAXN + 5];
int sg[MAXN + 5];
int Fa[MAXN + 5];
int sk[MAXN + 5];
int tot = 0;

#undef MAXN
 
void push (int u, int v) {
	node *p = new node;
	
	p->ed = v;
	p->next = s[u];
	s[u] = p;
}
int find (int x) {
	if (Fa[x] != Fa[Fa[x]]) {
		Fa[x] = find (Fa[x]);
	}
	
	return Fa[x];
}
void op (int x, int y) {
	Fa[find (x)] = find (y); 
}
void init (int now, int fa) {
	vis[now] = sc[now];
	for (node *i = s[now]; i; i = i->next) {
		if (i->ed == fa) {
			continue;
		}
		init (i->ed, now);
		vis[now] &= vis[i->ed];
	}
}
void download (trie *p, int loc) {
	if (!p) {
		return ;
	}
	if ((!p->next[0] && !p->next[1]) || !p->Lazy) {
		p->Lazy = 0;
		
		return ;
	}
	if (p->Lazy & (1 << loc)) {
		swap (p->next[0], p->next[1]);
	}
	if (p->next[0]) {
		p->next[0]->Lazy ^= p->Lazy;
	}
	if (p->next[1]) {
		p->next[1]->Lazy ^= p->Lazy;
	}
	p->Lazy = 0;
}
void change (trie *r, int now, int v) {
	stack <trie*> S;
	
	for (int i = MAXM; i >= 0; i--) {
		S.push(r);
		
		bool dir = ((1 << i) & v);
		
		if (!r->next[dir]) {
			r->next[dir] = new trie;
		}
		r = r->next[dir];
	}
	r->num = 1;
	if (!r->k) {
		r->k = ++tot;
		Fa[tot] = tot;
	}
	sk[now] = r->k;
	while (!S.empty ()) {
		S.top()->num = 0;
		if (S.top()->next[0]) {
			S.top()->num += S.top()->next[0]->num;
		}
		if (S.top()->next[1]) {
			S.top()->num += S.top()->next[1]->num;
		}
		S.pop();
	}
}
void merge (trie *&a, trie *b, int d) {
	if (a == 0) {
		a = b;
		
		return ;
	}
	else if (b == 0) {
		return ;
	}
	if (d < 0) {
		op (a->k, b->k);
		a->num = (a->num | b->num);
		
		return ;
	}
	download (a, d);
	download (b, d);
	merge (a->next[0], b->next[0], d - 1);
	merge (a->next[1], b->next[1], d - 1);
	a->num = 0;
	if (a->next[0]) {
		a->num += a->next[0]->num;
	}
	if (a->next[1]) {
		a->num += a->next[1]->num;
	}
}
int find (trie* a, int d) {
	if (!a || d < 0) {
		return 0;
	}
	download (a, d);
	if (a->next[0] && a->next[0]->num == (1 << d)) {
		return find (a->next[1], d -  1) | (1 << d);
	}
	else {
		return find (a->next[0], d - 1);
	}
}
void dfs (int now, int fa) {
	int sum = 0;
	
	for (node *i = s[now]; i; i = i->next) {
		if (i->ed == fa || vis[i->ed]) {
			continue;
		} 
		dfs (i->ed, now);
		sum ^= sg[i->ed];
	}
	for (node *i = s[now]; i; i = i->next) {
		if (i->ed == fa || vis[i->ed]) {
			continue;
		}
		rt[i->ed]->Lazy ^= (sum ^ sg[i->ed]);
		merge (rt[now], rt[i->ed], MAXM);
	}
	if (!rt[now]) {
		rt[now] = new trie;
	}
	if (!sc[now]) {
		change (rt[now], now, sum);
	}
	sg[now] = find (rt[now], MAXM);
}

int main () {
	int n;
	
	scanf ("%d", &n);
	for (int i = 1; i <= n; i ++) {
		int v;
		
		scanf ("%d", &v);
		sc[i] = v;
	}
	for (int i = 1; i < n; i ++) {
		int u, v;
		
		scanf ("%d %d", &u, &v);
		push (u, v);
		push (v, u);
	}
	init (1, 0);
	if (vis[1]) {
		printf ("-1");
		
		return 0;
	}
	dfs (1, 0);
	if (!sg[1]) {
		printf ("-1");
	}
	else {
		trie *p = rt[1];
		
		for (int i = MAXM; i >= 0; i --) {
			download(p, i);
			p = p->next[0];
		}
		for (int i = 1; i <= n; i ++) {
			if (find (sk[i]) == find(p->k)) {
				printf ("%d ", i);
			}
		}
	}
}

CF1034E

首先, 这是一道子集卷积的板子, 然后我们贺上去后会发现过不了, 考虑如何优化。
首先我们不考虑子集卷积, 就直接考虑 FWT 。 按照子集卷积的思路来, 把 \(j \& k = 0\) 这一限制改成 \(count(j) + count(k) = count(i)\) (其中 \(count(i)\) 表示 \(i\) 二进制下 \(1\) 的个数), 但是接着, 我们不考虑枚举, 而是选择再次转化, 变成 \(4^{count(j) + count(k)} = 4^{count(i)}\) 接着, 我们再考虑将每个 \(a_j\)\(b_k\) 都改写成 \(a_j * 4^{count(j)}\)\(b_k * 4^ {count(k)}\) 。 这时候, \(count (j) + count (k) < count(i)\) 肯定不可能发生(否则无法满足 \(j | k = i\) ) , 而 \(count (j) + count (k) > count(i)\) 时因为 \(4^{count(j) + count(k)} > 4^{count(i)}\) 所以除了以后会被 \(mod 4\) 模掉, 最后直接 FWT 即可。

posted @ 2023-03-16 22:44  小篪篪  阅读(51)  评论(0)    收藏  举报
Live2D