瀏覽代碼

add post/2021-nc-3

Xi Ruoyao 3 年之前
父節點
當前提交
a8a0bcc4e8
共有 2 個文件被更改,包括 181 次插入0 次删除
  1. 二進制
      content/post/2021-nc-3/i_am_joker.jpg
  2. 181 0
      content/post/2021-nc-3/index.md

二進制
content/post/2021-nc-3/i_am_joker.jpg


+ 181 - 0
content/post/2021-nc-3/index.md

@@ -0,0 +1,181 @@
++++
+date = "2021-07-24T20:30:00+08:00"
+draft = false
+tags = []
+title = "2021 牛客多校第 3 场"
+authors = ["xry111"]
++++
+
+# F. 24dian
+
+## 题意
+
+输入 $n$ 和 $m$,输出所有从扑克牌堆抽 $n$ 张牌 ($13^n$ 种情况) 后,
+获得的牌**能且仅能**通过中间过程有分数的四则运算得到数 $m$ 的情况。
+$1 \leq n \leq 4$,$1 \leq m \leq 10^9$。
+
+## 做法 (非标准)
+
+对于 $n \leq 2$,显然没有这样的情况。
+
+对于 $n = 3$,如果先用两张牌除出个分数,
+那么必然要再做一次乘法才能得到整数 $m$。那么只要先乘再除就没有分数了,
+所以也没有这样的情况。
+
+对于 $n = 4$,暴力枚举 $\binom{13}{4}$ 种情况,然后暴力枚举 $4!$ 种顺序,
+再暴力枚举 Catalan 数种加括号的方案,再暴力枚举 $4^3$ 种加运算符的方案,
+结果本地一跑发现 $1$ 秒跑不完。大概试了一下发现 $m \geq 300$ 无解,
+就直接打表过了。最后发现全场好像就两个队打表,
+人家别人写的搜索交上去都随便过。
+
+![我是小丑](i_am_joker.jpg)
+
+我们的表还被出题人讲题的时候不小心放到屏幕上了,
+然后出题人一看心想“woc 这什么傻逼”,赶紧切走了。
+
+# I. Kuriyama Mirai and Exclusive Or
+
+## 题意
+
+给你一个数列 $\\{a\_i\\}$,两种操作,一种对于 $i \\in [l, r]$ 把
+$a\_i$ 都异或 $x$,另一种对于 $i \\in [l, r]$ 把 $a\_i$ 都异或
+$(i - l + x)$。求所有操作结束后的数列。
+
+## 做法 (非标准)
+
+第一种操作就跟没有一样,放到校赛都能过 $114$ 人。
+
+对于第二种操作,我们按位讨论。操作对于第 $i (0 \\leq i)$ 位的影响是以
+$2^{i+1}$ 为周期的,其中前半周期没影响,后半周期对这一位异或 $1$
+(就是个方波)。如果 $i$ 较大,则操作相当于对 $n / 2^{i+1}$
+个区间做第一种操作,剩下就是 $i$ 较小的情况。
+
+对于 $i$ 较小的情况,我们把操作拆成左操作和右操作,然后对操作按位置排序。
+从左往右扫描序列,维护当前位置左边所有操作的总影响。这个影响一定是以
+$2^{i+1}$ 为周期的,可以存到一个 $2^{i+1}$ 位的二进制数里面。
+在计入新的操作时,这个操作的影响可以通过循环移 $x \\bmod 2^{i+1}$ 位
+(相当于方波的移相) 确定,然后异或到当前总影响上就行了。当位置向右移动时,
+需要将总影响循环移 $1$ 位。因为 $2^{i+1}$ 会超过计算机的字长 $w$,
+需要使用 `bitset` 存这个二进制数。
+
+考虑两种情况的阈值 $t$,对于 $i > t$ 按第一种情况做,其他按第二种情况做。
+时间代价大概可以写成
+
+$$\\sum\_{i = t + 1}^{29} \\frac{nq}{2^{i+1}} +
+\\sum\_{i = 0}^{t} \\frac{2^{i+1}(n+q)}{w} \\approx
+\\frac{nq}{2^{t+1}} + \frac{2^{t+2}(n+q)}{w}$$
+
+经过一通操作会发现这个东西是 $\\mathcal{O}(\\sqrt{\\frac{nq(n+q)}{w}})$ 的,
+能过。然而现场把最优的 $t$ 算错了,本地随机数据都过不了,
+赛后才想到可以胡乱试一些 $t$ 值的 qwq。
+
+注意为了达到这个复杂度,不能每次都用 `bitset<len>` (`len` 是 $2^{t+1}$),
+但是 C++ template 的语义又要求尖括号里面的那个数必须在编译期确定,
+所以这里通过 `fuck<t>` 调用 `fuck<t-1>` 这样的模板元编程 (TMP)
+技巧实现对 $i \leq t$ 的情况的处理。为了<del>毒瘤人</del>展示这种写法,
+把代码贴出来康康:
+
+```c++
+#include <bits/stdc++.h>
+using namespace std;
+
+static int a[600000];
+
+struct Q
+{
+	int tp, l, r, x;
+};
+
+static Q q[400000];
+static int f[600000];
+
+enum { t = 10 };
+
+struct Q1
+{
+	int l, x;
+	bool operator<(const Q1 &rhs) const
+	{
+		return l < rhs.l;
+	}
+};
+
+static Q1 q1[800000];
+
+template <int k>
+void fuck(int m, int n)
+{
+	bitset<(1 << k)> stat = 0;
+	bitset<(1 << k)> val = 0;
+
+	int b = 1 << (k - 1);
+	int period = b + b;
+	int mask = period - 1;
+
+	for (int i = b; i < period; i++)
+		val[i] = 1;
+
+	for (int i = 0, j = 0; i < n; i++) {
+		for (; j < m && q1[j].l <= i; j++) {
+			int x = q1[j].x & mask;
+			stat = stat ^ (val >> x) ^ (val << (period - x));
+		}
+		if (stat[0])
+			a[i] ^= b;
+		int xx = stat[0];
+		stat >>= 1;
+		stat[period - 1] = xx;
+	}
+
+	fuck<k - 1>(m, n);
+}
+
+template<> void fuck<0>(int, int) {}
+
+int main()
+{
+	int n, m;
+	scanf("%d%d", &n, &m);
+	for (int i = 0; i < n; i++)
+		scanf("%d", &a[i]);
+	for (int i = 0; i < m; i++)
+		scanf("%d%d%d%d", &q[i].tp, &q[i].l, &q[i].r, &q[i].x);
+	for (int i = 0; i < m; i++)
+		if (q[i].tp == 0) {
+			f[q[i].l - 1] ^= q[i].x;
+			f[q[i].r] ^= q[i].x;
+		} else {
+			for (int k = t + 1; k < 30; k++) {
+				int b = 1 << k;
+				int period = b + b;
+				int mask = period - 1;
+				int x = q[i].x & mask;
+				int xx = max(x, b);
+				int l = q[i].l + xx - x;
+				int r = l + mask - xx;
+				if (l <= q[i].r) {
+					f[l - 1] ^= b;
+					f[min(r, q[i].r)] ^= b;
+				}
+				for (l = r + b + 1; l <= q[i].r; l += period) {
+					f[l - 1] ^= b;
+					f[min(l + b - 1, q[i].r)] ^= b;
+				}
+			}
+		}
+	int acc = 0;
+	for (int i = 0; i < n; i++)
+		a[i] ^= (acc ^= f[i]);
+	int mm = 0;
+	for (int i = 0; i < m; i++)
+		if (q[i].tp == 1) {
+			q1[mm++] = {q[i].l - 1, q[i].x};
+			q1[mm++] = {q[i].r, q[i].x + (q[i].r - q[i].l + 1)};
+		}
+	sort(q1, q1 + mm);
+	fuck<t + 1>(mm, n);
+	for (int i = 0; i < n; i++)
+		printf("%d%c", a[i], " \n"[i + 1 == n]);
+	return 0;
+}
+```