[Record] NOI 2025 场外 vp+题解

前言

场外做今年 NOI 的题目,倒属于名副其实的信心赛,毕竟区分度太低,金银交界和铜银交界都是如此。虽然其实我的分数并不高,但是差距在哪里可以看得很清楚,只要肯下死功夫,很容易弥补。

这半年里除了竞赛没有太多其他任务。水平不好说,但可能是因为心态成熟一些了,偶有爆发,数据结构一如既往很弱,人类智慧也是我没有的,但常规的套路场能打到一个相对不错的分数了。

希望大家各自精彩。 希望我们共同精彩。

vp 过程及反思

省流:(假装笔试 \(100\)\(100+(100+63+8)+(100+16+35)=422\)。勉强过银牌线。
D1T2 调太久,没冲出来,血亏。D1T3 没打暴力,更亏。
考虑到今年题目难度以及正式比赛的心态影响,接下来一年时间需要在这个分数基础上提高不少才算稳当。

2025/07/15 vp Day 1

\(\texttt{T1 robot}\):开场看笑了,简单最短路。对于每个点只需拆出出度个点,原本的点可以不保留,总点数为 \(m\),感觉没有任何问题后开写,\(\texttt{9:05}\) 通过。

\(\texttt{T2 sequence}\):用了一点时间理解题意,A 性质是显然的,花了一点时间会了第一问的 \(O(n^3)\) 做法,并发现由于随机数据下第二问不会算重,且复杂度完全卡不满,因此能够通过所有 B 性质测试点。因为 \(\texttt{for}\) 循环预处理时忘记 \(\texttt{break}\) 这一智熄错误,调到 \(\texttt{12:55}\) 才通过这部分。发现优化是可行的,没时间写了。

\(\texttt{T3 tree}\):没怎么思考这道题的暴力,拿了 8pts 就回去看 T2 了。结束后发现 56pts 是非常显然的。


Reflection

队线 256pts 对应到每道题的具体做法,都是我的大脑能够理解,且再多给一定时间我能独立做出来的。这并不意味着我现在有多高的水平,今年 NOI 区分度太低,仅此而已。

T2 因为全程完全没有怀疑预处理会写错,浪费大量时间调试,以致于并未给 T3 的暴力或者 T2 的正解留足思考时间。另一方面,我几乎是调试到最后时刻压线完成了这档分数,如果在正式赛场上,我会不会因为心态影响,面对这个没有发现的小错误直接崩盘?

而且我观察到了正解的转移形式,却在 \(\text{dp}\) 的时候选择了一个极其难以转移的地方列方程,当计算极为复杂的时候,应该多去想一想是否真的一定要在我选择的地方统计答案,而不是对着一个复杂的计算死磕优化。

T3 思考时间有限,但从另一个角度而言,一直在尝试做 AB 两个特殊性质,而完全没有考虑 \(O(\alpha n^2)\) 的暴力分,也是没有拿到大众分的原因。即使有更多的时间,如果我并不能跳出这一错误思路,我依然不可能做出来这档分。


2025/07/17 vp Day2

\(\texttt{T1 ternary}\):手玩了半个小时左右注意到答案只会被 \(110\)\(101\) 形式的子串影响,\(\texttt{9:15}\) 通过。

\(\texttt{T2 set}\)\(O(2^{3n})\) 的暴力 \(\text{dp}\) 是容易做的,根本没往推式子的方向想,故而止步于此。

\(\texttt{T3 defense}\):答案显然是可以二分的,\(\text{check}\) 的策略思考了一阵子。容易发现如果当前可以使用攻击牌代替防御牌打出,且后续的攻击牌依然满足数量需求,则打出攻击牌比打出防御牌更优。处理防守牌数量的前缀和后求出前缀和数组的后缀 \(\min\) 就可以做到 \(O(n)\ \text{check}\)。前缀和的后缀 \(\min\) 显然很好线段树维护,但是询问……算了,不会。

罚坐两小时。


Reflection

纯粹水平达不到,推式子的能力还非常弱,FWT 也是不会的。这点分数已经算是目前能力能够支持的最好发挥了。


题解

D1T1 机器人 robot

朴素建分层图是 \(O(nk)\) 级别,但由于 \(p>d_u\) 时,拆出的这一个点没有出边指向别的点,即这个点只会作为转移的终点,因此建出该点是完全没有必要的。

因此对于原图中的每个点 \(u\) 拆出 \(d_u\) 个点,跑一遍最短路,最后再统一更新一遍 \(p>d_u\) 的转移即可。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 3e5+10;
const int M = 1e6+10;
const LL INF = 0x3f3f3f3f3f3f3f3fLL;
#define I inline

int c, n, m, k, tot;
int v[N], w[N], d[N];
LL sum[N];
vector<pair<int, int>> edge[N];
vector<int> idx[N];

struct Edge { int to, next; LL w; } e[M];
int head[M], cnt;
I void add(int u, int v, LL w) {
	e[cnt] = Edge{v, head[u], w};
	head[u] = cnt++;
}

struct Node {
	int u; LL d;
	I friend bool operator < (const Node& x, const Node& y) {
		return x.d > y.d;
	}
};
priority_queue<Node> q;
LL dis[M], rst[N];
bool vis[M];
void dijkstra(int s) {
	memset(dis, 0x3f, sizeof dis);
	memset(vis, 0, sizeof vis);
	q.push(Node{s, dis[s] = 0});
	while (!q.empty()) {
		int u = q.top().u; q.pop();
		if (vis[u]) continue;
		vis[u] = true;
		for (int i = head[u]; ~i; i = e[i].next) {
			int v = e[i].to;
			if (dis[v] > dis[u]+e[i].w) {
				dis[v] = dis[u]+e[i].w;
				q.push(Node{v, dis[v]});
			}
		}
	}
}

int main() {
	freopen("robot.in", "r", stdin);
	freopen("robot.out", "w", stdout);
	scanf("%d", &c);
	scanf("%d%d%d", &n, &m, &k);
	for (int i = 1; i < k; i++) scanf("%d", &v[i]);
	for (int i = 2; i <= k; i++) scanf("%d", &w[i]);
	for (int i = 1; i <= k; i++) sum[i] = sum[i-1]+w[i];
	for (int i = 1; i <= n; i++) {
		scanf("%d", &d[i]);
		idx[i].emplace_back(0);
		for (int j = 1; j <= d[i]; j++) idx[i].emplace_back(++tot);
		edge[i].emplace_back(0, 0);
		for (int j = 1; j <= d[i]; j++) {
			int y, z;
			scanf("%d%d", &y, &z);
			edge[i].emplace_back(y, z);
		}
	}
	memset(head, -1, sizeof head);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < d[i]; j++) add(idx[i][j], idx[i][j+1], v[j]);
		for (int j = d[i]; j > 1; j--) add(idx[i][j], idx[i][j-1], w[j]);
		for (int j = 1; j <= d[i]; j++) {
			int u = edge[i][j].first, w = edge[i][j].second;
			if (j > d[u]) {
				add(idx[i][j], idx[u][d[u]], sum[j]-sum[d[u]]+w);
			} else {
				add(idx[i][j], idx[u][j], w);
			}
		}
	}
	dijkstra(idx[1][1]);
	memset(rst, 0x3f, sizeof rst);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= d[i]; j++) {
			rst[i] = min(rst[i], dis[idx[i][j]]);
			int u = edge[i][j].first, w = edge[i][j].second;
			rst[u] = min(rst[u], dis[idx[i][j]]+w);
		}
	}
	for (int i = 1; i <= n; i++) {
		if (rst[i] == INF) rst[i] = -1;
		printf("%lld ", rst[i]);
	}
	return 0;
}

D1T2 序列变换 sequence

如果将操作按照操作方向连成箭头,如操作 \(i,i+1\) 且此时满足 \(a_{i+1}\geq a_i\),则从 \(i\)\(i+1\) 连一个右箭头,反之,从 \(i+1\)\(i\) 连左箭头。

最终对序列的操作类似如下形式(竖线表示分隔独立段):

\[\longrightarrow\triangle\longleftarrow\mid\longrightarrow\triangle\longleftarrow\mid\longrightarrow\dots\longleftarrow\mid\longrightarrow \triangle\longleftarrow \]

可以在 \(\triangle\) 位置进行 \(\text{dp}\),可能到达它的左右箭头显然都是 \(O(n)\) 级别的。

\(\text{Left},\text{Right}\) 分别为左右箭头集合,\(val_x\) 表示箭头 \(x\) 按照其所指方向以此操作一遍后,\(\triangle\) 位置被减少的值。发现对于某个位置,可以转移的左右箭头需要满足的条件为 \(val_x+val_y\leq a_i\)\(x\in\text{Left},y\in\text{Right}\))。

将左右箭头集合按照 \(val\) 排序,双指针解决这一约束。

这样可以 \(O(n^2\log n)\) 地解决第一问。

对于第二问,这样计算可能出现算重的情况,原因在于若在一个箭头按其方向依次计算的过程中,某一元素被消为 \(0\),此时操作它与不操作它得到的序列是完全相同的。因此直接通过引入判断条件,排除掉这一情况即可。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 5e3+10;
const int MOD = 1e9+7;
const LL INF = 1e18;
#define add(a, b) ((a+=b)>=MOD)&&(a-=MOD)

int id, T;
int n, a[N], b[N], c[N];
struct Node { LL sum; int prd, val, idx; };
vector<Node> L[N], R[N];
LL f[N], g[N];

void preparation() {
	for (int i = 1; i <= n; i++) L[i].clear(), R[i].clear();
	for (int i = 1; i <= n; i++) {
		LL sum = b[i]; int prd = c[i], val = a[i];
		for (int j = i+1; j <= n; j++) {
			if (a[j] < val || !val) break;
			L[j].push_back(Node{sum, prd, val, i});
			sum += b[j];
			prd = (LL)prd*c[j]%MOD;
			val = a[j]-val;
		}
		sum = b[i], prd = c[i], val = a[i];
		for (int j = i-1; j >= 1; j--) {
			if (a[j] <= val) break;
			R[j].push_back(Node{sum, prd, val, i});
			sum += b[j];
			prd = (LL)prd*c[j]%MOD;
			val = a[j]-val;
		}
	}
	for (int i = 1; i <= n; i++) {
		sort(L[i].begin(), L[i].end(), [](const Node& x, const Node& y) {
			return x.val < y.val;
		});
		sort(R[i].begin(), R[i].end(), [](const Node& x, const Node& y) {
			return x.val > y.val;
		});
	}
}

void solve() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
	preparation();
	for (int i = 1; i <= n; i++) f[i] = -INF, g[i] = 0;
	f[0] = 0; g[0] = 1;
	for (int i = 1; i <= n; i++) {
		f[i] = max(f[i], f[i-1]);
		add(g[i], g[i-1]);
		auto it = L[i].begin();
		int sum = 0;
		for (auto x : L[i]) {
			if (x.val == a[i]) {
				f[i] = max(f[i], f[x.idx-1]+x.sum+b[i]);
				add(g[i], (LL)g[x.idx-1]*x.prd%MOD*c[i]%MOD);
			} else {
				f[i] = max(f[i], f[x.idx-1]+x.sum);
				add(g[i], (LL)g[x.idx-1]*x.prd%MOD);
			}
		}
		for (auto x : R[i]) {
			f[x.idx] = max(f[x.idx], f[i-1]+x.sum);
			add(g[x.idx], (LL)g[i-1]*x.prd%MOD);
		}
		LL mx = -INF;
		for (auto x : R[i]) {
			while (it != L[i].end() && it->val+x.val < a[i]) {
				mx = max(mx, f[it->idx-1]+it->sum);
				add(sum, (LL)g[it->idx-1]*it->prd%MOD);
				++it;
			}
			f[x.idx] = max(f[x.idx], mx+x.sum);
			add(g[x.idx], (LL)sum*x.prd%MOD);
		}
	}
	printf("%lld %lld\n", f[n], g[n]);
}

int main() {
	freopen("sequence.in", "r", stdin);
	freopen("sequence.out", "w", stdout);
	scanf("%d%d", &id, &T);
	while (T--) solve();
	return 0;
}

D2T1 三目运算符 ternary

观察过程本身确实是层层递进的,但是这里实在想不到对观察过程特别有逻辑的叙述。

显然答案只与 \(110,101\) 两种连续段有关,具体可以分类讨论如下:

  • 若序列中存在 \(110\),记其中 \(0\) 的位置为 \(p\),则答案为 \(n-p+1\)
  • 若序列中不存在 \(110\) 且存在 \(101\),答案为 \(1\)
  • 若序列中既不存在 \(110\) 也不存在 \(101\),答案为 \(0\)

对于这两种连续段的维护,固然可以维护一堆东西来分类讨论,但不妨直接维护线段树区间的前后缀 \(0/1\) 数量,拼接即可得到答案。非常好写,常数也小。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 4e5+10;
const int INF = 0x3f3f3f3f;
#define I inline

int c, t, n, q;
char str[N];

namespace S100 {
	struct TreeNode {
		int pre[2], suf[2], pos[2], flg[2], lazy;
		I void Swap() {
			swap(pre[0], pre[1]);
			swap(suf[0], suf[1]);
			swap(pos[0], pos[1]);
			swap(flg[0], flg[1]);
		}
	} tree[N<<2];
	#define mid ((l+r)>>1)
	#define lc(x) ((x)<<1)
	#define rc(x) ((x)<<1|1)
	I void pushup(int x, int l, int r) {
		tree[x].pos[0] = min(tree[lc(x)].pos[0], tree[rc(x)].pos[0]);
		tree[x].pos[1] = min(tree[lc(x)].pos[1], tree[rc(x)].pos[1]);
		tree[x].flg[0] = tree[lc(x)].flg[0]|tree[rc(x)].flg[0];
		tree[x].flg[1] = tree[lc(x)].flg[1]|tree[rc(x)].flg[1];
		if (tree[lc(x)].suf[1]+tree[rc(x)].pre[1] >= 2 && tree[rc(x)].pre[1] < r-mid)
			tree[x].pos[0] = min(tree[x].pos[0], mid+tree[rc(x)].pre[1]+1);
		if (tree[lc(x)].suf[0]+tree[rc(x)].pre[0] >= 2 && tree[rc(x)].pre[0] < r-mid)
			tree[x].pos[1] = min(tree[x].pos[1], mid+tree[rc(x)].pre[0]+1);
		if (tree[lc(x)].suf[0] == 1 && mid-l+1 >= 2 && tree[rc(x)].pre[1] >= 1)
			tree[x].flg[0] = true;
		if (tree[rc(x)].pre[0] == 1 && r-mid >= 2 && tree[lc(x)].suf[1] >= 1)
			tree[x].flg[0] = true;
		if (tree[lc(x)].suf[1] == 1 && mid-l+1 >= 2 && tree[rc(x)].pre[0] >= 1)
			tree[x].flg[1] = true;
		if (tree[rc(x)].suf[1] == 1 && r-mid >= 2 && tree[lc(x)].suf[0] >= 1)
			tree[x].flg[1] = true;
		tree[x].pre[0] = tree[lc(x)].pre[0];
		if (tree[lc(x)].pre[0] == mid-l+1) tree[x].pre[0] += tree[rc(x)].pre[0];
		tree[x].pre[1] = tree[lc(x)].pre[1];
		if (tree[lc(x)].pre[1] == mid-l+1) tree[x].pre[1] += tree[rc(x)].pre[1];
		tree[x].suf[0] = tree[rc(x)].suf[0];
		if (tree[rc(x)].suf[0] == r-mid) tree[x].suf[0] += tree[lc(x)].suf[0];
		tree[x].suf[1] = tree[rc(x)].suf[1];
		if (tree[rc(x)].suf[1] == r-mid) tree[x].suf[1] += tree[lc(x)].suf[1];
	}
	I void pushdown(int x) {
		if (tree[x].lazy) {
			tree[lc(x)].lazy ^= 1;
			tree[lc(x)].Swap();
			tree[rc(x)].lazy ^= 1;
			tree[rc(x)].Swap();
			tree[x].lazy = 0;
		}
	}
	void build(int x = 1, int l = 1, int r = n) {
		tree[x].lazy = 0;
		if (l == r) {
			tree[x].flg[0] = tree[x].flg[1] = 0;
			tree[x].pos[0] = tree[x].pos[1] = INF;
			if (str[l] == '0') {
				tree[x].pre[0] = tree[x].suf[0] = 1;
				tree[x].pre[1] = tree[x].suf[1] = 0;
			} else {
				tree[x].pre[1] = tree[x].suf[1] = 1;
				tree[x].pre[0] = tree[x].suf[0] = 0;
			}
			return;
		}
		build(lc(x), l, mid);
		build(rc(x), mid+1, r);
		pushup(x, l, r);
	}
	void modify(int ql, int qr, int x = 1, int l = 1, int r = n) {
		if (ql <= l && qr >= r) {
			tree[x].Swap(); tree[x].lazy ^= 1;
			return;
		}
		pushdown(x);
		if (ql <= mid) modify(ql, qr, lc(x), l, mid);
		if (qr > mid) modify(ql, qr, rc(x), mid+1, r);
		pushup(x, l, r);
	}
	void solve() {
		build();
		LL rst = 0;
		// cout << tree[1].pos[0] << " " << tree[1].flg[0] << endl;
		if (tree[1].pos[0] != INF) rst = n-tree[1].pos[0]+1;
		else if (tree[1].flg[0]) rst = 1;
		for (int i = 1; i <= q; i++) {
			int l, r;
			scanf("%d%d", &l, &r);
			modify(l, r);
			LL now = 0;
			// cout << tree[1].pos[0] << " " << tree[1].flg[0] << endl;
			if (tree[1].pos[0] != INF) now = n-tree[1].pos[0]+1;
			else if (tree[1].flg[0]) now = 1;
			rst ^= now*(i+1);
		}
		printf("%lld\n", rst);
	}
}

void solve() {
	scanf("%d%d%s", &n, &q, str+1);
	S100::solve();
}

int main() {
	freopen("ternary.in", "r", stdin);
	freopen("ternary.out", "w", stdout);
	scanf("%d%d", &c, &t);
	while (t--) solve();
	return 0;
}

D2T2 集合 set

最不擅长的一类推式子,将二进制数看作二进制位的集合,\(P,Q\) 即为它们构成的幂集,\(f(S)\) 即为幂集 \(S\) 中的所有集合求交得到的集合。

\(U_0=\{i\}_{i=0}^{n-1}\)\(U_1=\{S\mid S\subseteq U_0\}\),设 \(w(S)=\prod_{x\in S}a_x\)

开始推式子:

\[\begin{align*} ans&=\sum_{S\subseteq U_0}\sum_{P\subseteq U_1,f(P)=S}\sum_{Q\subseteq U_1,f(Q)=S,P\cap Q=\varnothing} w(P\cup Q)\\ &=\sum_{S\subseteq U_0}\sum_{P\subseteq U_1,f(P)=S}w(P)\sum_{Q\subseteq U_1,f(Q)=S,P\cap Q=\varnothing}w(Q)\\ &=\sum_{S\subseteq U_0}\sum_{S\subseteq T_p\subseteq U_0}(-1)^{|T_p|-|S|}\sum_{P\subseteq U_1,T_p\subseteq f(P)}w(P)\sum_{S\subseteq T_q\subseteq U_0}(-1)^{|T_q|-|S|}\sum_{Q\subseteq U_1,T_q\subseteq f(Q),P\cap Q=\varnothing}w(Q) \qquad 超集反演\\ &=\sum_{T_p\subseteq U_0}\sum_{T_q\subseteq U_0}(-1)^{|T_p|+|T_q|}\sum_{S\subseteq T_p,S\subseteq T_q}\sum_{P\subseteq U_1,T_p\subseteq f(P)}w(P)\sum_{Q\subseteq U_1,T_q\subseteq f(Q),P\cap Q=\varnothing}w(Q)\\ &=\sum_{T_p\subseteq U_0}\sum_{T_q\subseteq U_0}(-1)^{|T_p|+|T_q|}\cdot 2^{|T_p\cup T_q|}\sum_{P\subseteq U_1,T_p\subseteq f(P)}w(P)\sum_{Q\subseteq U_1,T_q\subseteq f(Q),P\cap Q=\varnothing}w(Q)\\ \end{align*} \]

考虑转换 \(\sum_{P\subseteq U_1,T_p\subseteq f(P)}w(P)\sum_{Q\subseteq U_1,T_p\subseteq f(Q),P\subseteq Q=\varnothing}w(Q)\),显然设 \(S_p,S_q\)\(T_p,T_q\) 所有超集构成的集合,这个式子可以转换为 \(\sum_{P\subseteq S_p}w(P)\sum_{Q\subseteq S_q,P\cap Q=\varnothing}w(Q)\)

此时考察式子的组合意义进行转换,若集合 \(X\in S_p\cap S_q\)\(X\) 可以在 \(P,Q\) 中产生贡献,也可以不选;否则只能在 \(P,Q\) 其中一个产生贡献或者不选。因此该式等价于 \(\prod_{X\in S_p\cap S_q}(2a_X+1)\prod_{X\in S_p\triangle S_q}(a_X+1)\)

显然 \(S_q\cap S_q\)\(T_p\cup T_q\) 的全体超集构成的集合,集合对称差使用容斥拆开,可以继续化简。

\(A(T)=\prod_{S\subseteq T}(2a_S+1),B(T)=\prod_{S\subseteq T}{a_S+1}\),根据如上过程推式子如下:

\[\begin{align*} ans&=\sum_{T_p\subseteq U_0}\sum_{T_q\subseteq U_0}(-1)^{|T_p|+|T_q|}\cdot 2^{|T_p\cup T_q|}\sum_{P\subseteq U_1,T_p\subseteq f(P)}w(P)\sum_{Q\subseteq U_1,T_q\subseteq f(Q),P\cap Q=\varnothing}w(Q)\\ &=\sum_{T_p\subseteq U_0}\sum_{T_q\subseteq U_0}(-1)^{|T_p|+|T_q|}\cdot 2^{|T_p\cup T_q|}\sum_{P\subseteq S_p} w(P)\sum_{Q\subseteq S_q,P\cap Q=\varnothing}w(Q)\\ &=\sum_{T_p\subseteq U_0}\sum_{T_q\subseteq U_0}(-1)^{|T_p|+|T_q|}\cdot 2^{|T_p\cup T_q|}\prod_{X\in S_p\cup S_q}(2a_x+1)\prod_{X\in S_p\triangle S_q}(a_X+1)\\ &=\sum_{T_p\subseteq U_0}\sum_{T_q\subseteq U_0}(-1)^{|T_p|+|T_q|}\cdot 2^{|T_p\cup T_q|}\cdot\frac{A(T_p\cup T_q)B(T_p)B(T_q)}{B(T_p\cup T_q)^2} \end{align*} \]

再设 \(C(T)=\dfrac{A(T)}{2^{|T|}B(T)^2},D(T)=(-1)^{|T|}2^{|T|}B(T)\),答案式如下:

\[\begin{align*} ans&=\sum_{T_p,T_q\subseteq U_0}C(T_p\cup T_q)D(T_p)D(T_q)\\ &=\sum_{S\subseteq U_0}C(S)\sum_{T_p\cup T_q=S}D(T_p)D(T-q) \end{align*} \]

发现式子为或卷积,因此直接 FWT 即可。

对于分母为 \(0\) 的情况,在分母上带 \(\epsilon\) 即可。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = (1<<20)+10;
const int MOD = 998244353;
const int inv2 = (MOD+1)/2;
#define I inline

int id, T, n, S;
int a[N], pw[N], cnt[N];

int qmont(int x, int k) {
	int rst = 1;
	while (k) {
		if (k & 1) rst = (LL)rst*x%MOD;
		x = (LL)x*x%MOD; k >>= 1;
	}
	return rst;
}

struct Number {
	int x, z;
	Number(int x_ = 0) {
		if (x_) x = x_, z = 0;
		else x = z = 1;
	}
	Number(int x_, int z_): x(x_), z(z_) {}
	I friend Number inv(const Number& a) { return Number(qmont(a.x, MOD-2), -a.z); }
	I friend Number operator + (const Number& a, const Number& b) {
		if (a.z == b.z) return Number((a.x+b.x)%MOD, a.z);
		return (a.z < b.z) ? a : b;
	}
	I friend Number operator - (const Number& a, const Number& b) {
		if (a.z == b.z) return Number((a.x-b.x+MOD)%MOD, a.z);
		return (a.z < b.z) ? a : Number((MOD-b.x)%MOD, b.z);
	}
	I friend Number operator * (const Number& a, const Number& b) {
		return Number((LL)a.x*b.x%MOD, a.z+b.z);
	}
};
Number A[N], B[N], C[N], D[N], invB[N], tmp[N];

void Mult(Number* arr) {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < S; j++)
			if (!(j&(1<<i))) arr[j] = arr[j]*arr[j^1<<i];
	}
}
void FWT(Number* arr, int flag) { // 快速沃尔什变换模版
	if (~flag) {
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < S; j++)
				if (j&(1<<i)) arr[j] = arr[j]+arr[j^1<<i];
		}
	} else {
		for (int i = n-1; ~i; i--) {
			for (int j = 0; j < S; j++)
				if (j&(1<<i)) arr[j] = arr[j]-arr[j^1<<i];
		}
	}
}

void preparation() {
	pw[0] = 1;
	for (int i = 1; i <= 20; i++) pw[i] = (LL)pw[i-1]*inv2%MOD;
	for (int i = 1; i < N; i++) cnt[i] = cnt[i^(i&(-i))]+1;
}

void solve() {
	scanf("%d", &n);
	S = 1<<n;
	for (int i = 0; i < S; i++) scanf("%d", &a[i]);
	for (int i = 0; i < S; i++) {
		A[i] = Number((a[i]*2+1)%MOD);
		B[i] = Number((a[i]+1)%MOD);
	}
	Mult(A); Mult(B);
	for (int i = 0; i < S; i++) invB[i] = inv(B[i]);
	for (int i = 0; i < S; i++)
		C[i] = A[i]*invB[i]*invB[i]*pw[cnt[i]];
	for (int i = 0; i < S; i++)
		D[i] = B[i]*(1<<cnt[i])*((cnt[i]&1)?MOD-1:1);
	FWT(D, 1);
	for (int i = 0; i < S; i++) tmp[i] = D[i]*D[i];
	FWT(tmp, -1);
	int rst = 0;
	for (int i = 0; i < S; i++) {
		Number now = C[i]*tmp[i];
		if (!now.z) ((rst += now.x)>=MOD) && (rst -= MOD);
	}
	printf("%d\n", rst);
}

int main() {
	freopen("set.in", "r", stdin);
	freopen("set.out", "w", stdout);
	scanf("%d%d", &id, &T);
	preparation();
	while (T--) solve();
	return 0;
}
posted @ 2025-07-20 00:22  剑履山河  阅读(53)  评论(0)    收藏  举报